Compare commits

2 Commits

Author SHA1 Message Date
mjallen18
0aa9a0f994 fmt 2026-03-30 19:34:40 -05:00
mjallen18
9728f49e42 fmt 2026-03-30 19:16:09 -05:00
9 changed files with 580 additions and 225 deletions

View File

@@ -138,7 +138,7 @@
# variable assignments for simple strings (e.g. username=admin). # variable assignments for simple strings (e.g. username=admin).
# shellcheck SC2209 flags this as a warning, breaking the build when # shellcheck SC2209 flags this as a warning, breaking the build when
# the value matches a command name. Exclude SC2209 globally. # the value matches a command name. Exclude SC2209 globally.
(final: prev: { (_final: prev: {
writeShellApplication = writeShellApplication =
args: args:
prev.writeShellApplication ( prev.writeShellApplication (

View File

@@ -31,6 +31,7 @@ in
desktop.plasma = enabled; desktop.plasma = enabled;
programs = { programs = {
opencode = enabled;
thunderbird = enabled; thunderbird = enabled;
hyprland = { hyprland = {
enable = false; enable = false;

View File

@@ -147,6 +147,21 @@ rec {
"Extra environment variables passed to the service"; "Extra environment variables passed to the service";
reverseProxy = mkReverseProxyOpt name; reverseProxy = mkReverseProxyOpt name;
hostedService = {
enable = mkOpt types.bool (cfg.reverseProxy.enable
) "Expose this service in Glance dashboard (auto-enabled when reverseProxy is on)";
title = mkOpt types.str name "Display title in Glance";
icon = mkOpt types.str "si:glance" "Icon identifier for Glance (e.g. si:actualbudget)";
group = mkOpt types.str "Services" "Glance group/category for this service";
url = mkOpt types.str (
if cfg.reverseProxy.enable then
"https://${cfg.reverseProxy.subdomain}.${cfg.reverseProxy.domain}"
else
"http://127.0.0.1:${toString cfg.port}"
) "Service URL for Glance (auto-derived from reverseProxy if enabled)";
basicAuth = mkOpt types.bool false "Require basic auth for this service in Glance";
};
} }
// options; // options;
}; };
@@ -298,6 +313,8 @@ rec {
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Option creation helpers # Option creation helpers
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Option creation helpers
# ---------------------------------------------------------------------------
mkOpt = mkOpt =
type: default: description: type: default: description:

View File

@@ -69,10 +69,11 @@ in
mission-center mission-center
parted parted
vesktop vesktop
] ++ ( ]
if isArm then ++ (
if isArm then
[ ] [ ]
else else
[ [
proton-pass proton-pass
] ]

View File

@@ -92,9 +92,7 @@ in
type = mkOpt types.str "wifi" "type of the network.(wifi/ethernet)"; type = mkOpt types.str "wifi" "type of the network.(wifi/ethernet)";
interface = interface = mkOpt types.str "" "Interface for this profile (defaults to global ipv4.interface).";
mkOpt types.str ""
"Interface for this profile (defaults to global ipv4.interface).";
autoconnect = mkBoolOpt true "autoconnect to this connection"; autoconnect = mkBoolOpt true "autoconnect to this connection";

View File

@@ -4,10 +4,62 @@
namespace, namespace,
... ...
}: }:
with lib;
let let
name = "glance"; name = "glance";
cfg = config.${namespace}.services.${name}; cfg = config.${namespace}.services.${name};
net = lib.${namespace}.network;
hostedServiceSites =
let
servicesCfg = config.${namespace}.services;
serviceNames = builtins.attrNames servicesCfg;
in
builtins.concatMap (
serviceName:
let
serviceCfg = servicesCfg.${serviceName};
hosted = serviceCfg.hostedService or null;
in
if hosted != null && hosted.enable then
[
(
{
title = hosted.title;
url = hosted.url;
icon = hosted.icon;
}
// optionalAttrs hosted.basicAuth {
basic-auth = {
username = "\${ARR_USER}";
password = "\${ARR_PASS}";
};
}
)
]
else
[ ]
) serviceNames;
hostedServicesByGroup = builtins.groupBy (svc: svc.hostedService.group) (
builtins.filter (svc: svc.hostedService.enable) (
builtins.map (
serviceName:
let
serviceCfg = config.${namespace}.services.${serviceName};
in
{
inherit (serviceCfg) hostedService;
}
) (builtins.attrNames config.${namespace}.services)
)
);
makeMonitorWidget = title: sites: {
type = "monitor";
cache = "1m";
inherit title;
inherit sites;
};
glanceConfig = lib.${namespace}.mkModule { glanceConfig = lib.${namespace}.mkModule {
inherit config name; inherit config name;
@@ -18,6 +70,259 @@ let
default = cfg.dataDir; default = cfg.dataDir;
description = "Path to the NAS pool mount to display in server-stats."; description = "Path to the NAS pool mount to display in server-stats.";
}; };
enableHostedServices = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Auto-discover services with hostedService.enable and add them to the monitor widget";
};
hostedServiceGroups = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Create separate monitor widgets for each hostedService group instead of one combined widget";
};
extraSites = lib.mkOption {
type = lib.types.listOf (
lib.types.submodule {
options = {
title = lib.mkOption {
type = lib.types.str;
description = "Display title";
};
url = lib.mkOption {
type = lib.types.str;
description = "Service URL";
};
icon = lib.mkOption {
type = lib.types.str;
default = "si:glance";
description = "Icon identifier (e.g. si:servicename)";
};
allow-insecure = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Allow insecure connections";
};
basic-auth = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Require basic auth (uses ARR credentials)";
};
};
}
);
default = [ ];
description = "Extra sites to display in the monitor widget";
};
weather = lib.mkOption {
type = lib.types.submodule {
options = {
enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Enable weather widget";
};
location = lib.mkOption {
type = lib.types.str;
default = "Saint Paul, Minnesota, United States";
description = "Weather location";
};
units = lib.mkOption {
type = lib.types.enum [
"imperial"
"metric"
];
default = "imperial";
description = "Temperature units";
};
hour-format = lib.mkOption {
type = lib.types.enum [
"12h"
"24h"
];
default = "12h";
description = "Hour format";
};
};
};
default = { };
description = "Weather widget settings";
};
bookmarks = lib.mkOption {
type = lib.types.listOf (
lib.types.submodule {
options = {
title = lib.mkOption {
type = lib.types.str;
description = "Group title";
};
links = lib.mkOption {
type = lib.types.listOf (
lib.types.submodule {
options = {
title = lib.mkOption {
type = lib.types.str;
description = "Link title";
};
url = lib.mkOption {
type = lib.types.str;
description = "Link URL";
};
};
}
);
description = "List of links";
};
};
}
);
default = [
{
title = "General";
links = [
{
title = "Gmail";
url = "https://mail.google.com/mail/u/0/";
}
{
title = "Proton Mail";
url = "https://mail.proton.me/u/0/inbox";
}
{
title = "MyNixOS";
url = "https://www.mynixos.com/";
}
{
title = "Github";
url = "https://github.com/";
}
];
}
{
title = "Entertainment";
links = [
{
title = "YouTube";
url = "https://www.youtube.com/";
}
{
title = "Prime Video";
url = "https://www.primevideo.com/";
}
{
title = "Disney+";
url = "https://www.disneyplus.com/";
}
];
}
{
title = "Social";
links = [
{
title = "Reddit";
url = "https://www.reddit.com/";
}
{
title = "Twitter";
url = "https://twitter.com/";
}
{
title = "Instagram";
url = "https://www.instagram.com/";
}
];
}
];
description = "Bookmark groups";
};
reddit = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [
"hockey"
"formula1"
];
description = "Subreddits to show in Reddit widgets";
};
search = lib.mkOption {
type = lib.types.listOf (
lib.types.submodule {
options = {
title = lib.mkOption {
type = lib.types.str;
description = "Search engine title";
};
shortcut = lib.mkOption {
type = lib.types.str;
description = "Bang shortcut (e.g. !yt)";
};
url = lib.mkOption {
type = lib.types.str;
description = "Search URL with {QUERY} placeholder";
};
};
}
);
default = [
{
title = "YouTube";
shortcut = "!yt";
url = "https://www.youtube.com/results?search_query={QUERY}";
}
];
description = "Custom search engine bangs";
};
servers = lib.mkOption {
type = lib.types.listOf (
lib.types.submodule {
options = {
name = lib.mkOption {
type = lib.types.str;
description = "Server name";
};
cpu-temp-sensor = lib.mkOption {
type = lib.types.str;
default = "";
description = "CPU temp sensor path";
};
mountpoints = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule {
options = {
name = lib.mkOption {
type = lib.types.str;
description = "Display name for mountpoint";
};
};
}
);
description = "Mountpoints to display";
};
};
}
);
default = [
{
name = "Jallen-NAS";
cpu-temp-sensor = "/sys/devices/pci0000:00/0000:00:08.1/0000:cd:00.0/hwmon/hwmon*/temp1_input";
mountpoints = {
"/home" = {
name = "Home";
};
"${cfg.nasPoolPath}" = {
name = "nas_pool";
};
};
}
];
description = "Servers for server-stats widget";
};
}; };
moduleConfig = { moduleConfig = {
services.glance = { services.glance = {
@@ -33,7 +338,6 @@ let
{ {
name = "Startpage"; name = "Startpage";
width = "default"; width = "default";
# tab = "First";
hide-desktop-navigation = true; hide-desktop-navigation = true;
center-vertically = true; center-vertically = true;
columns = [ columns = [
@@ -44,236 +348,73 @@ let
type = "calendar"; type = "calendar";
first-day-of-week = "sunday"; first-day-of-week = "sunday";
} }
{ ]
type = "weather"; ++ (lib.mkIf cfg.weather.enable {
units = "imperial"; type = "weather";
hour-format = "12h"; units = cfg.weather.units;
location = "Saint Paul, Minnesota, United States"; hour-format = cfg.weather.hour-format;
} location = cfg.weather.location;
{ })
type = "server-stats"; ++ (lib.mkIf (cfg.servers != [ ]) {
servers = [ type = "server-stats";
{ servers = cfg.servers;
type = "local"; });
name = "Jallen-NAS";
cpu-temp-sensor = "/sys/devices/pci0000:00/0000:00:08.1/0000:cd:00.0/hwmon/hwmon*/temp1_input"; # Tctl
mountpoints = {
"/home" = {
name = "Home";
};
"${cfg.nasPoolPath}" = {
name = "nas_pool";
};
};
}
];
}
];
} }
{ {
size = "full"; size = "full";
# tab = "First";
widgets = [ widgets = [
{ {
type = "search"; type = "search";
autofocus = true; autofocus = true;
search-engine = "google"; search-engine = "google";
bangs = [ bangs = cfg.search;
{
title = "YouTube";
shortcut = "!yt";
url = "https://www.youtube.com/results?search_query={QUERY}";
}
];
} }
{ ]
++ (lib.mkIf cfg.hostedServiceGroups (
builtins.map (
groupName:
makeMonitorWidget groupName (
builtins.map (svc: {
title = svc.hostedService.title;
url = svc.hostedService.url;
icon = svc.hostedService.icon;
}) (hostedServicesByGroup.${groupName} or [ ])
)
) (builtins.attrNames hostedServicesByGroup)
))
++ (lib.mkIf (!cfg.hostedServiceGroups && cfg.enableHostedServices) [
(makeMonitorWidget "Services" hostedServiceSites)
])
++ (lib.mkIf (cfg.extraSites != [ ]) (
builtins.map (site: {
type = "monitor"; type = "monitor";
cache = "1m"; cache = "1m";
title = "Services"; title = site.title;
sites = [ sites = [
{ (
title = "Actual"; {
url = "https://actual.mjallen.dev/"; title = site.title;
icon = "si:actualbudget"; url = site.url;
} icon = site.icon;
{ }
title = "Jellyfin"; // optionalAttrs site.allow-insecure { allow-insecure = true; }
url = "https://jellyfin.mjallen.dev/"; )
icon = "si:jellyfin";
}
{
title = "Gitea";
url = "https://gitea.mjallen.dev/";
icon = "si:gitea";
}
{
title = "Nextcloud";
url = "https://cloud.mjallen.dev/";
icon = "si:nextcloud";
}
{
title = "Immich";
url = "https://immich.mjallen.dev/";
icon = "si:immich";
}
{
title = "AdGuard Home";
url = "http://${net.hosts.pi5.lan}:${toString net.ports.pi5.adguard}/";
icon = "si:adguard";
allow-insecure = true;
}
{
title = "Home Assistant";
url = "https://hass.mjallen.dev/";
icon = "si:homeassistant";
}
{
title = "Manyfold";
url = "http://${net.hosts.nas.lan}:${toString net.ports.nas.manyfold}/collections";
icon = "sh:manyfold";
allow-insecure = true;
}
{
title = "Code Server";
url = "http://${net.hosts.nas.lan}:${toString net.ports.nas.codeServer}/";
icon = "si:vscodium";
allow-insecure = true;
}
{
title = "NAS KVM";
url = "http://nas-kvm.local/";
icon = "si:nanokvm";
allow-insecure = true;
}
{
title = "Sonarr";
url = "http://${net.hosts.nas.lan}:${toString net.ports.nas.sonarr}/";
icon = "si:sonarr";
allow-insecure = true;
basic-auth = {
username = "\${ARR_USER}";
password = "\${ARR_PASS}";
};
}
{
title = "Radarr";
url = "http://${net.hosts.nas.lan}:${toString net.ports.nas.radarr}/";
icon = "si:radarr";
allow-insecure = true;
basic-auth = {
username = "\${ARR_USER}";
password = "\${ARR_PASS}";
};
}
{
title = "Sabnzbd";
url = "http://${net.hosts.nas.lan}:${toString net.ports.nas.sabnzbd}/";
icon = "si:sabnzbd";
allow-insecure = true;
basic-auth = {
username = "\${ARR_USER}";
password = "\${ARR_PASS}";
};
}
# {
# title = "";
# url = "";
# icon = "si:";
# }
]; ];
} }) cfg.extraSites
{ ))
type = "bookmarks"; ++ (lib.mkIf (cfg.bookmarks != [ ]) {
# tab = "First"; type = "bookmarks";
groups = [ groups = cfg.bookmarks;
{ })
title = "General"; ++ (lib.mkIf (cfg.reddit != [ ]) (
links = [ builtins.map (subreddit: {
{
title = "Gmail";
url = "https://mail.google.com/mail/u/0/";
}
{
title = "Proton Mail";
url = "https://mail.proton.me/u/0/inbox";
}
{
title = "MyNixOS";
url = "https://www.mynixos.com/";
}
{
title = "Github";
url = "https://github.com/";
}
];
}
{
title = "Entertainment";
links = [
{
title = "YouTube";
url = "https://www.youtube.com/";
}
{
title = "Prime Video";
url = "https://www.primevideo.com/";
}
{
title = "Disney+";
url = "https://www.disneyplus.com/";
}
];
}
{
title = "Social";
links = [
{
title = "Reddit";
url = "https://www.reddit.com/";
}
{
title = "Twitter";
url = "https://twitter.com/";
}
{
title = "Instagram";
url = "https://www.instagram.com/";
}
];
}
];
}
{
type = "reddit"; type = "reddit";
subreddit = "hockey"; inherit subreddit;
} }) cfg.reddit
{ ));
type = "reddit";
subreddit = "formula1";
}
];
} }
]; ];
} }
# {
# name = "test";
# width = "default";
# hide-desktop-navigation = true;
# center-vertically = true;
# columns = [
# {
# size = "small";
# widgets = [
# {
# type = "adguard";
# url = "http://pi4.local:3000";
# username = "mjallen";
# password = "BogieDudie1";
# }
# ];
# }
# ];
# }
]; ];
}; };
}; };
@@ -284,7 +425,6 @@ in
imports = [ imports = [
glanceConfig glanceConfig
# Sops env-file for arr credentials (gated behind glance.enable)
{ {
config = lib.mkIf cfg.enable ( config = lib.mkIf cfg.enable (
lib.${namespace}.mkSopsEnvFile { lib.${namespace}.mkSopsEnvFile {

View File

@@ -8,7 +8,6 @@ with lib;
let let
name = "kavita"; name = "kavita";
cfg = config.${namespace}.services.${name}; cfg = config.${namespace}.services.${name};
rootUrl = "https://kavita.${namespace}.dev/";
kavitaConfig = lib.${namespace}.mkModule { kavitaConfig = lib.${namespace}.mkModule {
inherit config name; inherit config name;

View File

@@ -1,5 +1,5 @@
{ ... }: { ... }:
final: prev: { _final: prev: {
home-assistant = prev.home-assistant.override { home-assistant = prev.home-assistant.override {
packageOverrides = _self: super: { packageOverrides = _self: super: {
psnawp = super.psnawp.overridePythonAttrs (old: { psnawp = super.psnawp.overridePythonAttrs (old: {

View File

@@ -6,6 +6,7 @@
}: }:
let let
inherit (lib.${namespace}) enabled disabled; inherit (lib.${namespace}) enabled disabled;
net = lib.${namespace}.network;
in in
{ {
${namespace} = { ${namespace} = {
@@ -15,6 +16,10 @@ in
port = 3333; port = 3333;
createUser = true; createUser = true;
reverseProxy = enabled; reverseProxy = enabled;
hostedService = {
group = "Finance";
icon = "si:actualbudget";
};
}; };
ai = { ai = {
enable = true; enable = true;
@@ -29,6 +34,9 @@ in
enable = true; enable = true;
subdomain = "cache"; subdomain = "cache";
}; };
hostedService = {
group = "Dev";
};
}; };
authentik = { authentik = {
enable = true; enable = true;
@@ -36,6 +44,9 @@ in
port = 9000; port = 9000;
reverseProxy = enabled; reverseProxy = enabled;
environmentFile = "/run/secrets/jallen-nas/authentik-env"; environmentFile = "/run/secrets/jallen-nas/authentik-env";
hostedService = {
group = "Infrastructure";
};
redis = { redis = {
enable = true; enable = true;
port = 6379; port = 6379;
@@ -104,6 +115,10 @@ in
enable = true; enable = true;
port = 3000; port = 3000;
reverseProxy = enabled; reverseProxy = enabled;
hostedService = {
group = "Dev";
icon = "si:gitea";
};
}; };
guacd = { guacd = {
enable = true; enable = true;
@@ -112,6 +127,165 @@ in
glance = { glance = {
enable = true; enable = true;
port = 5555; port = 5555;
hostedServiceGroups = true;
weather = {
enable = true;
location = "Saint Paul, Minnesota, United States";
units = "imperial";
hour-format = "12h";
};
servers = [
{
name = "NAS";
mountpoints."/media/nas/main" = {
name = "Main Pool";
};
}
{
name = "Pi5";
mountpoints."/" = {
name = "Root";
};
}
];
bookmarks = [
{
title = "General";
links = [
{
title = "Gmail";
url = "https://mail.google.com/mail/u/0/";
}
{
title = "Proton Mail";
url = "https://mail.proton.me/u/0/inbox";
}
{
title = "MyNixOS";
url = "https://www.mynixos.com/";
}
{
title = "Github";
url = "https://github.com/";
}
];
}
{
title = "Entertainment";
links = [
{
title = "YouTube";
url = "https://www.youtube.com/";
}
{
title = "Prime Video";
url = "https://www.primevideo.com/";
}
{
title = "Disney+";
url = "https://www.disneyplus.com/";
}
];
}
{
title = "Social";
links = [
{
title = "Reddit";
url = "https://www.reddit.com/";
}
{
title = "Twitter";
url = "https://twitter.com/";
}
{
title = "Instagram";
url = "https://www.instagram.com/";
}
];
}
];
reddit = [
"hockey"
"formula1"
];
search = [
{
title = "YouTube";
shortcut = "!yt";
url = "https://www.youtube.com/results?search_query={QUERY}";
}
{
title = "Wikipedia";
shortcut = "!w";
url = "https://en.wikipedia.org/wiki/{QUERY}";
}
];
extraSites = [
{
title = "Home Assistant";
url = "http://${net.hosts.nuc.lan}:${toString net.ports.nuc.homeAssistant}/";
icon = "si:vscodium";
allow-insecure = true;
}
{
title = "ESPHome";
url = "http://${net.hosts.nuc.lan}:${toString net.ports.nuc.esphome}/";
icon = "si:vscodium";
allow-insecure = true;
}
{
title = "Sonarr";
url = "http://${net.hosts.nas.lan}:${toString net.ports.nas.sonarr}/";
icon = "si:sonarr";
allow-insecure = true;
basic-auth = true;
}
{
title = "Radarr";
url = "http://${net.hosts.nas.lan}:${toString net.ports.nas.radarr}/";
icon = "si:radarr";
allow-insecure = true;
basic-auth = true;
}
{
title = "Sabnzbd";
url = "http://${net.hosts.nas.lan}:${toString net.ports.nas.sabnzbd}/";
icon = "si:sabnzbd";
allow-insecure = true;
basic-auth = true;
}
{
title = "AdGuard";
url = "http://${net.hosts.pi5.lan}:${toString net.ports.pi5.adguard}/";
icon = "si:adguard";
allow-insecure = true;
}
{
title = "Manyfold";
url = "http://${net.hosts.nas.lan}:${toString net.ports.nas.manyfold}/collections";
icon = "sh:manyfold";
allow-insecure = true;
}
{
title = "Code Server";
url = "http://${net.hosts.nas.lan}:${toString net.ports.nas.codeServer}/";
icon = "si:vscodium";
allow-insecure = true;
}
{
title = "NAS KVM";
url = "http://nas-kvm.local/";
icon = "si:iterm2";
allow-insecure = true;
}
{
title = "NUC KVM";
url = "http://pikvm.local/";
icon = "si:raspberrypi";
allow-insecure = true;
}
];
}; };
glances = { glances = {
enable = true; enable = true;
@@ -131,17 +305,28 @@ in
enable = true; enable = true;
port = 2283; port = 2283;
reverseProxy = enabled; reverseProxy = enabled;
hostedService = {
group = "Media";
icon = "si:immich";
};
}; };
jellyfin = { jellyfin = {
enable = true; enable = true;
port = 8096; port = 8096;
reverseProxy = enabled; reverseProxy = enabled;
hostedService = {
group = "Media";
icon = "si:jellyfin";
};
}; };
jellyseerr = { jellyseerr = {
enable = true; enable = true;
port = 5055; port = 5055;
createUser = true; createUser = true;
reverseProxy = enabled; reverseProxy = enabled;
hostedService = {
group = "Media";
};
}; };
kavita = { kavita = {
enable = true; enable = true;
@@ -158,6 +343,9 @@ in
enable = true; enable = true;
port = 6754; port = 6754;
reverseProxy = enabled; reverseProxy = enabled;
hostedService = {
group = "Finance";
};
}; };
manyfold = { manyfold = {
enable = true; enable = true;
@@ -167,6 +355,10 @@ in
enable = true; enable = true;
port = 8448; port = 8448;
reverseProxy = enabled; reverseProxy = enabled;
hostedService = {
group = "Infrastructure";
icon = "si:element";
};
}; };
minecraft = disabled; minecraft = disabled;
mongodb = disabled; mongodb = disabled;
@@ -192,12 +384,19 @@ in
enable = true; enable = true;
subdomain = "cloud"; subdomain = "cloud";
}; };
hostedService = {
group = "Infrastructure";
icon = "si:nextcloud";
};
}; };
ntfy = { ntfy = {
enable = true; enable = true;
port = 2586; port = 2586;
createUser = true; createUser = true;
reverseProxy = enabled; reverseProxy = enabled;
hostedService = {
group = "Infrastructure";
};
}; };
ocis = disabled; ocis = disabled;
onlyoffice = { onlyoffice = {