feat[server]: storage migration finished
Some checks are pending
Build and Deploy / build (push) Waiting to run
Build and Deploy / deploy (push) Blocked by required conditions
Flake check / Check flake (push) Waiting to run

This commit is contained in:
Leon Schwarzäugl 2026-01-19 17:43:30 +01:00
parent c6539ed484
commit 3422a39da5
Signed by: swarsel
GPG key ID: 26A54C31F2A4FD84
94 changed files with 1963 additions and 1626 deletions

View file

@ -1,6 +1,7 @@
{ lib, config, ... }:
{ lib, config, pkgs, ... }:
let
moduleName = "uwsm";
cfg = config.programs.uwsm;
in
{
options.swarselmodules.${moduleName} = lib.mkEnableOption "${moduleName} settings";
@ -20,5 +21,39 @@ in
};
};
};
services.displayManager.sessionPackages =
let
mk_uwsm_desktop_entry =
opts:
(pkgs.writeTextFile {
name = "${opts.name}-uwsm";
text = ''
[Desktop Entry]
Name=${opts.prettyName} (UWSM)
Comment=${opts.comment}
Exec=${lib.getExe cfg.package} start -F -- ${opts.binPath} ${lib.strings.escapeShellArgs opts.extraArgs}
Type=Application
'';
destination = "/share/wayland-sessions/${opts.name}-uwsm.desktop";
derivationArgs = {
passthru.providedSessions = [ "${opts.name}-uwsm" ];
};
});
in
lib.mkForce (lib.mapAttrsToList
(
name: value:
mk_uwsm_desktop_entry {
inherit name;
inherit (value)
prettyName
comment
binPath
extraArgs
;
}
)
cfg.waylandCompositors);
};
}

View file

@ -15,6 +15,18 @@
isNormalUser = true;
uid = 1000;
autoSubUidGidRange = false;
subUidRanges = [
{
count = 65534;
startUid = 100001;
}
];
subGidRanges = [
{
count = 999;
startGid = 1001;
}
];
description = "Leon S";
password = lib.mkIf (minimal || config.swarselsystems.isPublic) "setup";
hashedPasswordFile = lib.mkIf (!minimal && !config.swarselsystems.isPublic) config.sops.secrets.main-user-hashed-pw.path;

View file

@ -32,8 +32,6 @@
# NOTE: this is needed, we dont import sevrer network module for microvms
globals.hosts.${config.node.name}.isHome = true;
fileSystems."/persist".neededForBoot = lib.mkForce true;
systemd.network.networks."10-vlan-services" = {
dhcpV6Config = {
WithoutRA = "solicit";

View file

@ -36,6 +36,10 @@ in
};
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [{ directory = "/var/lib/private/${serviceName}"; }];
};
services.anki-sync-server = {
enable = true;
port = servicePort;

View file

@ -7,6 +7,10 @@ in
options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
config = lib.mkIf config.swarselmodules.server.${serviceName} {
swarselmodules.server = {
postgresql = true;
};
topology.self.services.${serviceName}.info = "https://${serviceDomain}";
globals = {

View file

@ -42,12 +42,16 @@ in
homeServiceAddress = lib.mkIf isHome homeServiceAddress;
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }];
};
services = {
${serviceName} = {
enable = true;
user = serviceUser;
group = if cfg.enableNginx then nginxGroup else serviceGroup;
dataDir = "/Vault/data/${serviceName}";
dataDir = "/var/lib/${serviceName}";
settings = {
TZ = config.repo.secrets.common.location.timezone;
APP_URL = "https://${serviceDomain}";

View file

@ -44,9 +44,13 @@ in
};
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }];
};
services.${serviceName} = {
enable = true;
stateDir = "/Vault/data/${serviceName}";
stateDir = "/var/lib/${serviceName}";
user = serviceUser;
group = serviceGroup;
lfs.enable = lib.mkDefault true;

View file

@ -61,6 +61,10 @@ in
homeServiceAddress = lib.mkIf isHome homeServiceAddress;
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }];
};
services.${serviceName} =
let
inherit (config.repo.secrets.local.freshrss) defaultUser;
@ -71,7 +75,7 @@ in
virtualHost = serviceDomain;
baseUrl = "https://${serviceDomain}";
authType = "form";
dataDir = "/Vault/data/tt-rss";
dataDir = "/var/lib/freshrss";
passwordFile = config.sops.secrets.freshrss-pw.path;
};

View file

@ -1,6 +1,6 @@
{ self, lib, pkgs, config, globals, dns, confLib, ... }:
let
inherit (confLib.gen { name = "homebox"; port = 7745; }) servicePort serviceName serviceDomain serviceAddress proxyAddress4 proxyAddress6;
inherit (confLib.gen { name = "homebox"; port = 7745; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6;
inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules;
in
{
@ -13,6 +13,10 @@ in
icon = "${self}/files/topology-images/${serviceName}.png";
};
swarselmodules.server = {
postgresql = true;
};
users.persistentIds = {
homebox = confLib.mkIds 981;
};
@ -33,14 +37,14 @@ in
};
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }];
};
systemd.services.homebox = {
environment = {
TMPDIR = "/var/lib/homebox/.tmp";
};
serviceConfig = {
# ReadWritePaths = "/var/lib/homebox";
RuntimeDirectory = "homebox";
BindPaths = "/run/homebox:/var/lib/homebox/.tmp";
HOME = "/var/lib/homebox";
};
};

View file

@ -15,59 +15,61 @@ in
{
options = {
swarselmodules.server.ids = lib.mkEnableOption "enable persistent ids on server";
users.persistentIds = mkOption {
default = { };
description = ''
Maps a user or group name to its expected uid/gid values. If a user/group is
used on the system without specifying a uid/gid, this module will assign the
corresponding ids defined here, or show an error if the definition is missing.
'';
type = types.attrsOf (
types.submodule {
options = {
uid = mkOption {
type = types.nullOr types.int;
default = null;
description = "The uid to assign if it is missing in `users.users.<name>`.";
users = {
persistentIds = mkOption {
default = { };
description = ''
Maps a user or group name to its expected uid/gid values. If a user/group is
used on the system without specifying a uid/gid, this module will assign the
corresponding ids defined here, or show an error if the definition is missing.
'';
type = types.attrsOf (
types.submodule {
options = {
uid = mkOption {
type = types.nullOr types.int;
default = null;
description = "The uid to assign if it is missing in `users.users.<name>`.";
};
gid = mkOption {
type = types.nullOr types.int;
default = null;
description = "The gid to assign if it is missing in `users.groups.<name>`.";
};
};
gid = mkOption {
type = types.nullOr types.int;
default = null;
description = "The gid to assign if it is missing in `users.groups.<name>`.";
};
};
}
);
};
users.users = mkOption {
type = types.attrsOf (
types.submodule (
{ name, ... }:
{
config.uid =
let
persistentUid = cfg.${name}.uid or null;
in
mkIf (persistentUid != null) (mkDefault persistentUid);
}
)
);
};
);
};
users.groups = mkOption {
type = types.attrsOf (
types.submodule (
{ name, ... }:
{
config.gid =
let
persistentGid = cfg.${name}.gid or null;
in
mkIf (persistentGid != null) (mkDefault persistentGid);
}
)
);
users = mkOption {
type = types.attrsOf (
types.submodule (
{ name, ... }:
{
config.uid =
let
persistentUid = cfg.${name}.uid or null;
in
mkIf (persistentUid != null) (mkDefault persistentUid);
}
)
);
};
groups = mkOption {
type = types.attrsOf (
types.submodule (
{ name, ... }:
{
config.gid =
let
persistentGid = cfg.${name}.gid or null;
in
mkIf (persistentGid != null) (mkDefault persistentGid);
}
)
);
};
};
};
config = lib.mkIf config.swarselmodules.server.ids {

View file

@ -1,12 +1,16 @@
{ lib, pkgs, config, globals, dns, confLib, ... }:
let
inherit (confLib.gen { name = "immich"; port = 3001; }) servicePort serviceName serviceUser serviceDomain serviceAddress proxyAddress4 proxyAddress6;
inherit (confLib.gen { name = "immich"; port = 3001; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6;
inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules;
in
{
options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
config = lib.mkIf config.swarselmodules.server.${serviceName} {
swarselmodules.server = {
postgresql = true;
};
users = {
persistentIds = {
immich = confLib.mkIds 989;
@ -36,13 +40,21 @@ in
};
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [
{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }
{ directory = "/var/cache/${serviceName}"; user = serviceUser; group = serviceGroup; }
{ directory = "/var/lib/redis-${serviceName}"; user = "redis-${serviceUser}"; group = "redis-${serviceGroup}"; }
];
};
services.${serviceName} = {
enable = true;
package = pkgs.immich;
host = "0.0.0.0";
port = servicePort;
# openFirewall = true;
mediaLocation = "/Vault/Eternor/Immich"; # dataDir
mediaLocation = "/storage/Pictures/${serviceName}"; # dataDir
environment = {
IMMICH_MACHINE_LEARNING_URL = lib.mkForce "http://localhost:3003";
};

View file

@ -1,6 +1,6 @@
{ pkgs, lib, config, globals, dns, confLib, ... }:
let
inherit (confLib.gen { name = "jellyfin"; port = 8096; }) servicePort serviceName serviceUser serviceDomain serviceAddress proxyAddress4 proxyAddress6;
inherit (confLib.gen { name = "jellyfin"; port = 8096; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6;
inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf nginxAccessRules homeServiceAddress;
in
{
@ -51,6 +51,13 @@ in
# openFirewall = true; # this works only for the default ports
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [
{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }
{ directory = "/var/cache/${serviceName}"; user = serviceUser; group = serviceGroup; }
];
};
nodes = {
${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = {
"${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6;

View file

@ -1,6 +1,6 @@
{ pkgs, lib, config, globals, dns, confLib, ... }:
let
inherit (confLib.gen { name = "jenkins"; port = 8088; }) servicePort serviceName serviceDomain serviceAddress proxyAddress4 proxyAddress6;
inherit (confLib.gen { name = "jenkins"; port = 8088; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6;
inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules;
in
{
@ -23,13 +23,17 @@ in
};
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }];
};
services.jenkins = {
enable = true;
withCLI = true;
port = servicePort;
packages = [ pkgs.stdenv pkgs.git pkgs.jdk17 config.programs.ssh.package pkgs.nix ];
listenAddress = "0.0.0.0";
home = "/Vault/apps/${serviceName}";
home = "/var/lib/${serviceName}";
};
nodes = {

View file

@ -80,12 +80,19 @@ in
};
};
environment.persistence."/persist" = lib.mkIf config.swarselsystems.isImpermanence {
files = [
certPathBase
keyPathBase
];
environment.persistence = {
"/persist" = lib.mkIf config.swarselsystems.isImpermanence {
files = [
certPathBase
keyPathBase
];
};
"/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }];
};
};
systemd.services."generateSSLCert-${serviceName}" =
let
daysValid = 3650;

View file

@ -2,7 +2,7 @@
let
inherit (config.swarselsystems) sopsFile;
inherit (confLib.gen { name = "kavita"; port = 8080; }) servicePort serviceName serviceUser serviceDomain serviceAddress proxyAddress4 proxyAddress6;
inherit (confLib.gen { name = "kavita"; port = 8080; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6;
inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf nginxAccessRules homeServiceAddress;
in
{
@ -29,6 +29,10 @@ in
icon = "${self}/files/topology-images/${serviceName}.png";
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }];
};
globals = {
networks = {
${webProxyIf}.hosts = lib.mkIf isProxied {
@ -50,7 +54,7 @@ in
user = serviceUser;
settings.Port = servicePort;
tokenKeyFile = config.sops.secrets.kavita-token.path;
dataDir = "/Vault/data/${serviceName}";
dataDir = "/var/lib/${serviceName}";
};
nodes = {

View file

@ -1,6 +1,6 @@
{ self, lib, config, globals, dns, confLib, ... }:
let
inherit (confLib.gen { name = "koillection"; port = 2282; dir = "/Vault/data/koillection"; }) servicePort serviceName serviceUser serviceDir serviceDomain serviceAddress proxyAddress4 proxyAddress6;
inherit (confLib.gen { name = "koillection"; port = 2282; dir = "/var/lib/koillection"; }) servicePort serviceName serviceUser serviceDir serviceDomain serviceAddress proxyAddress4 proxyAddress6;
inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules;
serviceDB = "koillection";
@ -46,6 +46,10 @@ in
};
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [{ directory = "/var/lib/${serviceName}"; }];
};
virtualisation.oci-containers.containers = {
koillection = {
image = "koillection/koillection@${containerRev}";

View file

@ -1,7 +1,7 @@
{ self, lib, config, globals, dns, confLib, ... }:
let
inherit (config.swarselsystems) sopsFile;
inherit (confLib.gen { name = "mailserver"; dir = "/var/lib/dovecot"; user = "virtualMail"; group = "virtualMail"; port = 443; }) serviceName serviceDir servicePort serviceUser serviceGroup serviceAddress serviceDomain proxyAddress4 proxyAddress6;
inherit (confLib.gen { name = "mailserver"; dir = "/var/lib/dovecot"; user = "virtualMail"; group = "virtualMail"; port = 80; }) serviceName serviceDir servicePort serviceUser serviceGroup serviceAddress serviceDomain proxyAddress4 proxyAddress6;
inherit (confLib.static) isHome webProxy homeWebProxy dnsServer homeServiceAddress nginxAccessRules;
inherit (config.repo.secrets.local.mailserver) user1 alias1_1 alias1_2 alias1_3 alias1_4 user2 alias2_1 alias2_2 alias2_3 user3;
baseDomain = globals.domains.main;

View file

@ -1,7 +1,7 @@
{ self, lib, config, pkgs, globals, dns, confLib, ... }:
let
inherit (config.swarselsystems) sopsFile;
inherit (confLib.gen { name = "matrix"; user = "matrix-synapse"; port = 8008; }) servicePort serviceName serviceUser serviceDomain serviceAddress proxyAddress4 proxyAddress6;
inherit (confLib.gen { name = "matrix"; user = "matrix-synapse"; port = 8008; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6;
inherit (confLib.static) isHome isProxied webProxy homeWebProxy dnsServer homeProxyIf webProxyIf homeServiceAddress nginxAccessRules;
federationPort = 8448;
@ -21,6 +21,10 @@ in
options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
config = lib.mkIf config.swarselmodules.server.${serviceName} {
swarselmodules.server = {
postgresql = true;
};
environment.systemPackages = with pkgs; [
matrix-synapse
lottieconverter
@ -118,9 +122,18 @@ in
};
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [
{ directory = "/var/lib/matrix-synapse"; user = serviceUser; group = serviceGroup; }
{ directory = "/var/lib/mautrix-whatsapp"; user = "mautrix-whatsapp"; group = "mautrix-whatsapp"; }
{ directory = "/var/lib/mautrix-telegram"; user = "mautrix-telegram"; group = "mautrix-telegram"; }
{ directory = "/var/lib/mautrix-signal"; user = "mautrix-signal"; group = "mautrix-signal"; }
];
};
services = {
postgresql = {
enable = true;
initialScript = pkgs.writeText "synapse-init.sql" ''
CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
@ -147,7 +160,7 @@ in
matrix-synapse = {
enable = true;
dataDir = "/Vault/data/matrix-synapse";
dataDir = "/var/lib/matrix-synapse";
settings = {
app_service_config_files =
let

View file

@ -47,8 +47,10 @@ in
node-exporter = confLib.mkIds 987;
grafana = confLib.mkIds 974;
};
groups.nextcloud-exporter = { };
users = {
nextcloud-exporter = {
group = "nextcloud-exporter";
extraGroups = [ "nextcloud" ];
};
@ -78,10 +80,17 @@ in
};
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [
{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }
{ directory = "/var/lib/prometheus2"; user = prometheusUser; group = prometheusGroup; }
];
};
services = {
${serviceName} = {
enable = true;
dataDir = "/Vault/data/${serviceName}";
dataDir = "/var/lib/${serviceName}";
provision = {
enable = true;
datasources.settings = {

View file

@ -36,9 +36,13 @@ in
icon = "${self}/files/topology-images/${serviceName}.png";
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [{ directory = "/var/lib/${serviceName}"; user = "mpd"; group = "mpd"; }];
};
services.${serviceName} = {
enable = true;
musicDirectory = "/media";
musicDirectory = "/storage/Music";
user = serviceUser;
group = serviceGroup;
network = {

View file

@ -76,6 +76,10 @@ in
};
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }];
};
services.${serviceName} = {
enable = true;
# openFirewall = true;
@ -83,7 +87,7 @@ in
LogLevel = "debug";
Address = "0.0.0.0";
Port = servicePort;
MusicFolder = "/Vault/Eternor/Music";
MusicFolder = "/storage/Music";
PlaylistsPath = "./Playlists";
AutoImportPlaylists = false;
EnableSharing = true;

View file

@ -27,6 +27,13 @@ in
homeServiceAddress = lib.mkIf isHome homeServiceAddress;
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [
{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }
{ directory = "/var/lib/redis-${serviceName}"; user = serviceUser; group = serviceGroup; }
];
};
services = {
${serviceName} = {
enable = true;
@ -36,8 +43,8 @@ in
};
package = pkgs."nextcloud${nextcloudVersion}";
hostName = serviceDomain;
home = "/Vault/data/${serviceName}";
datadir = "/Vault/data/${serviceName}";
home = "/var/lib/${serviceName}";
datadir = "/var/lib/${serviceName}";
https = true;
configureRedis = true;
maxUploadSize = "4G";

View file

@ -10,6 +10,12 @@ in
avahi = confLib.mkIds 978;
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [
{ directory = "/var/cache/samba"; }
];
};
services = {
# add a user with sudo smbpasswd -a <user>
samba = {
@ -35,7 +41,7 @@ in
browseable = "yes";
"read only" = "no";
"guest ok" = "no";
path = "/Vault/Eternor";
path = "/storage";
writable = "true";
comment = "Eternor";
"valid users" = nfsUser;

View file

@ -84,8 +84,15 @@ in
networking.firewall.allowedTCPPorts = [ 80 443 ];
environment.persistence."/persist" = lib.mkIf config.swarselsystems.isImpermanence {
files = [ dhParamsPathBase ];
environment.persistence = {
"/persist" = lib.mkIf config.swarselsystems.isImpermanence {
files = [ dhParamsPathBase ];
};
"/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [
{ directory = "/var/cache/nginx"; user = "nginx"; group = "nginx"; }
];
};
};
services.nginx = {

View file

@ -139,6 +139,10 @@ in
};
};
users = {
persistentIds.oauth2-proxy = confLib.mkIds 966;
};
# needed for homeWebProxy
networking.firewall.allowedTCPPorts = [ servicePort ];

View file

@ -5,7 +5,7 @@ let
kanidmDomain = globals.services.kanidm.domain;
inherit (config.swarselsystems) mainUser;
inherit (config.repo.secrets.local) persons;
mailAddress = config.repo.secrets.common.mail.address4;
in
{
options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
@ -29,7 +29,7 @@ in
authorizations = [
{
user = mainUser;
principal = builtins.head persons.${mainUser}.mailAddresses;
principal = mailAddress;
inherit (config.services.opkssh.providers.kanidm) issuer;
}
];

View file

@ -44,11 +44,21 @@ in
};
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [
{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }
{ directory = "/var/lib/redis-${serviceName}"; user = "redis-${serviceUser}"; group = "redis-${serviceGroup}"; }
{ directory = "/var/lib/private/tika"; }
{ directory = "/var/cache/${serviceName}"; user = serviceUser; group = serviceGroup; }
{ directory = "/var/cache/private/tika"; }
];
};
services = {
${serviceName} = {
enable = true;
mediaDir = "/Vault/Eternor/Paperless";
dataDir = "/Vault/data/${serviceName}";
mediaDir = "/storage/Documents/${serviceName}";
dataDir = "/var/lib/${serviceName}";
user = serviceUser;
port = servicePort;
passwordFile = config.sops.secrets.paperless-admin-pw.path;

View file

@ -6,6 +6,10 @@
users.persistentIds.rtkit = confLib.mkIds 996;
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [{ directory = "/var/lib/pipewire"; user = "pipewire"; group = "pipewire"; }];
};
services.pipewire = {
enable = true;
pulse.enable = true;

View file

@ -15,6 +15,12 @@ in
oci-containers.backend = "podman";
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [
{ directory = "/var/lib/containers"; }
];
};
networking.nftables.firewall = lib.mkIf config.networking.nftables.enable {
zones.podman = {

View file

@ -2,7 +2,7 @@
let
inherit (confLib.gen { name = "postgresql"; port = 3254; }) serviceName;
postgresVersion = 14;
postgresDirPrefix = if config.swarselsystems.isCloud then "/var/lib" else "/Vault/data";
postgresDirPrefix = "/var/lib";
in
{
options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
@ -22,9 +22,14 @@ in
dataDir = "${postgresDirPrefix}/${serviceName}/${builtins.toString postgresVersion}";
};
};
environment.persistence."/persist".directories = lib.mkIf (config.swarselsystems.isImpermanence && config.swarselsystems.isCloud) [
{ directory = "/var/lib/postgresql"; user = "postgres"; group = "postgres"; mode = "0750"; }
];
environment.persistence = {
"/persist".directories = lib.mkIf (config.swarselsystems.isImpermanence && config.swarselsystems.isCloud) [
{ directory = "/var/lib/postgresql"; user = "postgres"; group = "postgres"; mode = "0750"; }
];
"/state".directories = lib.mkIf config.swarselsystems.isMicroVM [
{ directory = "/var/lib/postgresql"; user = "postgres"; group = "postgres"; mode = "0750"; }
];
};
};
}

View file

@ -35,6 +35,10 @@ in
topology.self.services.${serviceName}.info = "https://${serviceDomain}";
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }];
};
globals = {
networks = {
${webProxyIf}.hosts = lib.mkIf isProxied {
@ -67,7 +71,7 @@ in
htpasswd_encryption = "autodetect";
};
storage = {
filesystem_folder = "/Vault/data/radicale/collections";
filesystem_folder = "/var/lib/radicale/collections";
};
};
rights = {

View file

@ -1,65 +1,88 @@
{ lib, pkgs, config, ... }:
let
inherit (config.swarselsystems) sopsFile;
targets = config.swarselsystems.server.restic.targets;
in
{
options.swarselmodules.server.restic = lib.mkEnableOption "enable restic backups on server";
options.swarselsystems.server.restic = {
bucketName = lib.mkOption {
type = lib.types.str;
};
paths = lib.mkOption {
type = lib.types.listOf lib.types.str;
};
withPostgres = lib.mkOption {
type = lib.types.bool;
default = false;
targets = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule ({ name, ... }: {
options = {
bucketName = lib.mkOption {
type = lib.types.str;
default = name;
};
repository = lib.mkOption {
type = lib.types.str;
};
paths = lib.mkOption {
type = lib.types.listOf lib.types.str;
};
withPostgres = lib.mkOption {
type = lib.types.bool;
default = false;
};
};
}));
default = { };
};
};
config = lib.mkIf config.swarselmodules.server.restic {
sops = {
secrets = {
resticpw = { inherit sopsFile; };
resticaccesskey = { inherit sopsFile; };
resticsecretaccesskey = { inherit sopsFile; };
};
templates = {
"restic-env".content = ''
AWS_ACCESS_KEY_ID=${config.sops.placeholder.resticaccesskey}
AWS_SECRET_ACCESS_KEY=${config.sops.placeholder.resticsecretaccesskey}
'';
};
secrets =
lib.mkMerge (lib.mapAttrsToList
(name: _: {
"resticpw-${name}" = { inherit sopsFile; };
"resticaccesskey-${name}" = { inherit sopsFile; };
"resticsecretaccesskey-${name}" = { inherit sopsFile; };
})
targets);
templates =
lib.mkMerge (lib.mapAttrsToList
(name: _: {
"restic-env-${name}".content = ''
AWS_ACCESS_KEY_ID=${config.sops.placeholder."resticaccesskey-${name}"}
AWS_SECRET_ACCESS_KEY=${config.sops.placeholder."resticsecretaccesskey-${name}"}
'';
})
targets);
};
services.restic =
let
inherit (config.repo.secrets.local) resticRepo;
in
{
backups = {
"${config.swarselsystems.server.restic.bucketName}" = {
environmentFile = config.sops.templates."restic-env".path;
passwordFile = config.sops.secrets.resticpw.path;
inherit (config.swarselsystems.server.restic) paths;
services.restic.backups =
lib.mapAttrs'
(name: target:
lib.nameValuePair target.bucketName {
environmentFile =
config.sops.templates."restic-env-${name}".path;
passwordFile =
config.sops.secrets."resticpw-${name}".path;
inherit (target) paths repository;
pruneOpts = [
"--keep-daily 3"
"--keep-weekly 2"
"--keep-monthly 3"
"--keep-yearly 100"
];
backupPrepareCommand = ''
${pkgs.restic}/bin/restic prune
'';
repository = "${resticRepo}";
initialize = true;
timerConfig = {
OnCalendar = "03:00";
};
};
};
};
}
)
targets;
};
}

View file

@ -7,6 +7,8 @@ let
})
globals.networks.home-lan.vlans;
selectVLANs = vlans: map (vlan: { VLAN = globals.networks.home-lan.vlans.${vlan}.id; }) vlans;
lan1VLANs = selectVLANs [ "home" "devices" "guests" ];
lan2VLANs = selectVLANs [ "home" "devices" "services" ];
lan3VLANs = selectVLANs [ "home" "devices" "services" ];
lan4VLANs = lan3VLANs;
lan5VLANs = selectVLANs [ "home" "devices" "guests" ];
@ -186,9 +188,9 @@ in
Bridge = "br";
ConfigureWithoutCarrier = true;
};
inherit bridgeVLANs;
bridgeVLANs = lan1VLANs;
};
# wifi
# winters
"30-lan2" = {
matchConfig.MACAddress = config.repo.secrets.local.networking.networks.lan2.mac;
linkConfig.RequiredForOnline = "enslaved";
@ -196,7 +198,7 @@ in
Bridge = "br";
ConfigureWithoutCarrier = true;
};
inherit bridgeVLANs;
bridgeVLANs = lan2VLANs;
};
# summers
"30-lan3" = {

View file

@ -44,7 +44,7 @@ in
hostName = serviceDomain;
user = serviceUser;
group = serviceGroup;
dataDir = "/Vault/data/snipeit";
dataDir = "/var/lib/snipeit";
database = {
user = serviceUser;
port = mysqlPort;

View file

@ -27,6 +27,12 @@ in
# when another user connects, the service will crash and the new user will login
systemd.services.spotifyd.serviceConfig.RestartSec = lib.mkForce 1;
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [
{ directory = "/var/cache/private/spotifyd"; }
];
};
services.spotifyd = {
enable = true;
settings = {

View file

@ -75,12 +75,16 @@ in
};
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [{ directory = "/var/lib/${serviceName}"; user = serviceUser; group = serviceGroup; }];
};
services.${serviceName} = rec {
enable = true;
user = serviceUser;
group = serviceGroup;
dataDir = lib.mkDefault "/Vault/data/${serviceName}";
configDir = "${cfg.dataDir}/.config/${serviceName}";
dataDir = if config.swarselsystems.isMicroVM then "/storage/Documents/syncthing" else (lib.mkDefault "/var/lib/${serviceName}");
configDir = if config.swarselsystems.isMicroVM then "/var/lib/syncthing/.config/syncthing" else "${cfg.dataDir}/.config/${serviceName}";
guiAddress = "0.0.0.0:${builtins.toString servicePort}";
openDefaultPorts = lib.mkIf (!isProxied) true; # opens ports TCP/UDP 22000 and UDP 21027 for discovery
relay.enable = false;

View file

@ -1,7 +1,7 @@
{ self, pkgs, lib, config, confLib, ... }:
let
inherit (confLib.gen { name = "transmission"; }) serviceName serviceDomain;
inherit (confLib.static) isHome;
inherit (confLib.gen { name = "transmission"; port = 9091; }) serviceName servicePort serviceDomain;
inherit (confLib.static) isHome homeServiceAddress homeWebProxy nginxAccessRules;
lidarrUser = "lidarr";
lidarrGroup = lidarrUser;
@ -96,6 +96,16 @@ in
inherit isHome;
};
environment.persistence."/state" = lib.mkIf config.swarselsystems.isMicroVM {
directories = [
{ directory = "/var/lib/radarr"; user = radarrUser; group = radarrGroup; }
{ directory = "/var/lib/readarr"; user = readarrUser; group = readarrGroup; }
{ directory = "/var/lib/sonarr"; user = sonarrUser; group = sonarrGroup; }
{ directory = "/var/lib/lidarr"; user = lidarrUser; group = lidarrGroup; }
{ directory = "/var/lib/private/prowlarr"; user = prowlarrUser; group = prowlarrGroup; }
];
};
services = {
radarr = {
enable = true;
@ -103,7 +113,7 @@ in
group = radarrGroup;
settings.server.port = radarrPort;
openFirewall = true;
dataDir = "/Vault/data/radarr";
dataDir = "/var/lib/radarr";
};
readarr = {
enable = true;
@ -111,7 +121,7 @@ in
group = readarrGroup;
settings.server.port = readarrPort;
openFirewall = true;
dataDir = "/Vault/data/readarr";
dataDir = "/var/lib/readarr";
};
sonarr = {
enable = true;
@ -119,7 +129,7 @@ in
group = sonarrGroup;
settings.server.port = sonarrPort;
openFirewall = true;
dataDir = "/Vault/data/sonarr";
dataDir = "/var/lib/sonarr";
};
lidarr = {
enable = true;
@ -127,53 +137,88 @@ in
group = lidarrGroup;
settings.server.port = lidarrPort;
openFirewall = true;
dataDir = "/Vault/data/lidarr";
dataDir = "/var/lib/lidarr";
};
prowlarr = {
enable = true;
settings.server.port = prowlarrPort;
openFirewall = true;
};
};
nginx = {
nodes = {
${homeWebProxy}.services.nginx = {
upstreams = {
transmission = {
servers = {
"${homeServiceAddress}:${builtins.toString servicePort}" = { };
};
};
radarr = {
servers = {
"${homeServiceAddress}:${builtins.toString radarrPort}" = { };
};
};
readarr = {
servers = {
"${homeServiceAddress}:${builtins.toString readarrPort}" = { };
};
};
sonarr = {
servers = {
"${homeServiceAddress}:${builtins.toString sonarrPort}" = { };
};
};
lidarr = {
servers = {
"${homeServiceAddress}:${builtins.toString lidarrPort}" = { };
};
};
prowlarr = {
servers = {
"${homeServiceAddress}:${builtins.toString prowlarrPort}" = { };
};
};
};
virtualHosts = {
"${serviceDomain}" = {
enableACME = false;
forceSSL = false;
acmeRoot = null;
extraConfig = nginxAccessRules;
locations = {
"/" = {
proxyPass = "http://localhost:9091";
proxyPass = "http://transmission";
extraConfig = ''
client_max_body_size 0;
'';
};
"/radarr" = {
proxyPass = "http://localhost:${builtins.toString radarrPort}";
proxyPass = "http://radarr";
extraConfig = ''
client_max_body_size 0;
'';
};
"/readarr" = {
proxyPass = "http://localhost:${builtins.toString readarrPort}";
proxyPass = "http://readarr";
extraConfig = ''
client_max_body_size 0;
'';
};
"/sonarr" = {
proxyPass = "http://localhost:${builtins.toString sonarrPort}";
proxyPass = "http://sonarr";
extraConfig = ''
client_max_body_size 0;
'';
};
"/lidarr" = {
proxyPass = "http://localhost:${builtins.toString lidarrPort}";
proxyPass = "http://lidarr";
extraConfig = ''
client_max_body_size 0;
'';
};
"/prowlarr" = {
proxyPass = "http://localhost:${builtins.toString prowlarrPort}";
proxyPass = "http://prowlarr";
extraConfig = ''
client_max_body_size 0;
'';

View file

@ -21,7 +21,7 @@ in
confLib = rec {
getConfig = if nixosConfig == null then config else nixosConfig;
gen = { name ? "n/a", user ? name, group ? name, dir ? null, port ? null, domain ? (domainDefault name), address ? addressDefault, proxy ? proxyDefault }: rec {
gen = { name ? "n/a", user ? name, group ? user, dir ? null, port ? null, domain ? (domainDefault name), address ? addressDefault, proxy ? proxyDefault }: rec {
servicePort = port;
serviceName = name;
specificServiceName = "${name}-${config.node.name}";
@ -88,65 +88,76 @@ in
mkMicrovm =
if config.swarselsystems.withMicroVMs then
(guestName:
{ enableStorage ? false
{ eternorPaths ? [ ]
, withZfs ? false
, ...
}:
{
${guestName} = {
backend = "microvm";
autostart = true;
zfs = lib.mkIf withZfs {
# stateful config that should be backed up
"/state" = {
pool = "Vault";
dataset = "guests/${guestName}/state";
};
# data that should be backed up
"/storage" = lib.mkIf enableStorage {
pool = "Vault";
dataset = "guests/${guestName}/storage";
};
# other stuff that should only reside on disk, not backed up
"/persist" = {
pool = "Vault";
dataset = "guests/${guestName}/persist";
};
};
modules = [
(config.node.configDir + /guests/${guestName}/default.nix)
{
node.secretsDir = config.node.configDir + /secrets/${guestName};
node.configDir = config.node.configDir + /guests/${guestName};
networking.nftables.firewall = {
zones.untrusted.interfaces = lib.mkIf
(
lib.length config.guests.${guestName}.networking.links == 1
)
config.guests.${guestName}.networking.links;
};
}
"${self}/modules/nixos/optional/microvm-guest.nix"
"${self}/modules/nixos/optional/systemd-networkd-base.nix"
];
microvm = {
system = config.node.arch;
baseMac = config.repo.secrets.local.networking.networks.lan.mac;
interfaces.vlan-services = {
mac = lib.mkForce "02:${lib.substring 3 5 config.guests.${guestName}.microvm.baseMac}:${mkDeviceMac globals.networks.home-lan.vlans.services.hosts."${config.node.name}-${guestName}".id}";
${guestName} =
{
backend = "microvm";
autostart = true;
zfs = lib.mkIf withZfs
({
# stateful config usually bind-mounted to /var/lib/ that should be backed up remotely
"/state" = {
pool = "Vault";
dataset = "guests/${guestName}/state";
};
# other stuff that should only reside on zfs, not backed up remotely
"/persist" = {
pool = "Vault";
dataset = "guests/${guestName}/persist";
};
} // lib.optionalAttrs (eternorPaths != [ ])
(lib.listToAttrs (map
# data that is pulled in externally by services, some of which is backed up externally
(eternorPath:
lib.nameValuePair "/storage/${eternorPath}" {
pool = "Vault";
dataset = "Eternor/${eternorPath}";
})
eternorPaths)));
modules = [
(config.node.configDir + /guests/${guestName}/default.nix)
{
node.secretsDir = config.node.configDir + /secrets/${guestName};
node.configDir = config.node.configDir + /guests/${guestName};
networking.nftables.firewall = {
zones.untrusted.interfaces = lib.mkIf
(
lib.length config.guests.${guestName}.networking.links == 1
)
config.guests.${guestName}.networking.links;
};
fileSystems = {
"/persist".neededForBoot = true;
} // lib.optionalAttrs withZfs {
"/state".neededForBoot = true;
};
}
"${self}/modules/nixos/optional/microvm-guest.nix"
"${self}/modules/nixos/optional/systemd-networkd-base.nix"
];
microvm = {
system = config.node.arch;
baseMac = config.repo.secrets.local.networking.networks.lan.mac;
interfaces.vlan-services = {
mac = lib.mkForce "02:${lib.substring 3 5 config.guests.${guestName}.microvm.baseMac}:${mkDeviceMac globals.networks.home-lan.vlans.services.hosts."${config.node.name}-${guestName}".id}";
};
};
extraSpecialArgs = {
inherit (inputs.self) nodes;
inherit (inputs.self.pkgs.${config.node.arch}) lib;
inherit inputs outputs minimal;
inherit (inputs) self;
withHomeManager = false;
microVMParent = config.node.name;
globals = inputs.self.globals.${config.node.arch};
};
};
extraSpecialArgs = {
inherit (inputs.self) nodes;
inherit (inputs.self.pkgs.${config.node.arch}) lib;
inherit inputs outputs minimal;
inherit (inputs) self;
withHomeManager = false;
microVMParent = config.node.name;
globals = inputs.self.globals.${config.node.arch};
};
};
}) else
(_: {
_ = { };