{ config, lib, namespace, ... }: with lib; let name = "glance"; cfg = config.${namespace}.services.${name}; 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 != null && svc.hostedService.enable) ( builtins.map ( serviceName: let serviceCfg = config.${namespace}.services.${serviceName}; in { hostedService = serviceCfg.hostedService or null; } ) (builtins.attrNames config.${namespace}.services) ) ); makeMonitorWidget = title: sites: { type = "monitor"; cache = "1m"; inherit title; inherit sites; }; glanceConfig = lib.${namespace}.mkModule { inherit config name; description = "glance"; options = { nasPoolPath = lib.mkOption { type = lib.types.str; default = cfg.dataDir; 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 = { services.glance = { enable = true; openFirewall = true; environmentFile = config.sops.templates."glance.env".path; settings = { server = { host = "0.0.0.0"; port = cfg.port; }; pages = [ { name = "Startpage"; width = "default"; hide-desktop-navigation = true; center-vertically = true; columns = [ { size = "small"; widgets = [ { type = "calendar"; first-day-of-week = "sunday"; } ] ++ lib.optional cfg.weather.enable { type = "weather"; units = cfg.weather.units; hour-format = cfg.weather.hour-format; location = cfg.weather.location; } ++ lib.optional (cfg.servers != [ ]) { type = "server-stats"; servers = cfg.servers; }; } { size = "full"; widgets = [ { type = "search"; autofocus = true; search-engine = "google"; bangs = cfg.search; } ] ++ lib.optionals 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.optionals (!cfg.hostedServiceGroups && cfg.enableHostedServices) [ (makeMonitorWidget "Services" hostedServiceSites) ] ++ lib.optionals (cfg.extraSites != [ ]) ( builtins.map (site: { type = "monitor"; cache = "1m"; title = site.title; sites = [ ( { title = site.title; url = site.url; icon = site.icon; } // optionalAttrs site.allow-insecure { allow-insecure = true; } ) ]; }) cfg.extraSites ) ++ lib.optional (cfg.bookmarks != [ ]) { type = "bookmarks"; groups = cfg.bookmarks; } ++ lib.optionals (cfg.reddit != [ ]) ( builtins.map (subreddit: { type = "reddit"; inherit subreddit; }) cfg.reddit ); } ]; } ]; }; }; }; }; in { imports = [ glanceConfig { config = lib.mkIf cfg.enable ( lib.${namespace}.mkSopsEnvFile { name = "glance.env"; restartUnit = "glance.service"; secrets = { "jallen-nas/glance/arr-username" = { }; "jallen-nas/glance/arr-password" = { }; }; content = '' ARR_USER=${config.sops.placeholder."jallen-nas/glance/arr-username"} ARR_PASS=${config.sops.placeholder."jallen-nas/glance/arr-password"} ''; } ); } ]; }