This commit is contained in:
mjallen18
2026-03-18 22:43:29 -05:00
parent d9f17670e1
commit af840f242b
49 changed files with 1079 additions and 1307 deletions

View File

@@ -1,10 +1,18 @@
{ {
pkgs, pkgs,
config, config,
inputs,
namespace, namespace,
... ...
}: }:
{ {
# steam-rom-manager HM module is needed for the steam-rom-manager program
# options. On NixOS hosts it's provided via sharedModules; here we add it
# explicitly so the standalone homeConfiguration build also includes it.
imports = [
inputs.steam-rom-manager.homeManagerModules.default
];
home = { home = {
username = "admin"; username = "admin";
packages = packages =

View File

@@ -17,12 +17,9 @@ let
in in
rec { rec {
# Conditionally enable modules based on system # ---------------------------------------------------------------------------
enableForSystem = # NixOS service module helpers
system: modules: # ---------------------------------------------------------------------------
builtins.filter (
mod: mod.systems or [ ] == [ ] || builtins.elem system (mod.systems or [ ])
) modules;
# Create a NixOS module with standard options (enable, port, reverseProxy, # Create a NixOS module with standard options (enable, port, reverseProxy,
# firewall, user, postgresql, redis) and optional caller-supplied options and # firewall, user, postgresql, redis) and optional caller-supplied options and
@@ -61,7 +58,6 @@ rec {
''; '';
}; };
# Open firewall
networking.firewall = lib.mkIf cfg.openFirewall { networking.firewall = lib.mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.port ]; allowedTCPPorts = [ cfg.port ];
allowedUDPPorts = [ cfg.port ]; allowedUDPPorts = [ cfg.port ];
@@ -76,11 +72,8 @@ rec {
groups.${name} = { }; groups.${name} = { };
}; };
# Ensure the service waits for the filesystem that hosts configDir and # RequiresMountsFor is silently ignored when the paths live on the root
# dataDir to be mounted before starting. RequiresMountsFor is the # filesystem, so this is safe on non-NAS hosts too.
# idiomatic systemd way to express this: if the paths live on the root
# filesystem the directive is silently ignored, so it is safe on every
# host — not just the NAS.
systemd.services.${serviceName}.unitConfig.RequiresMountsFor = [ systemd.services.${serviceName}.unitConfig.RequiresMountsFor = [
cfg.configDir cfg.configDir
cfg.dataDir cfg.dataDir
@@ -107,10 +100,6 @@ rec {
{ lib, ... }: { lib, ... }:
{ {
imports = [ imports = [
# defaultConfig and moduleConfig are kept as separate inline modules so
# the NixOS module system handles all merging (mkIf, mkForce, mkMerge,
# etc.) correctly, rather than merging raw attrsets with // or
# recursiveUpdate which can silently clobber mkIf wrappers.
{ config = lib.mkIf cfg.enable defaultConfig; } { config = lib.mkIf cfg.enable defaultConfig; }
{ config = lib.mkIf cfg.enable moduleConfig; } { config = lib.mkIf cfg.enable moduleConfig; }
]; ];
@@ -165,6 +154,147 @@ rec {
}; };
}; };
# Wraps mkModule for Podman/OCI container services. Generates all the
# standard mkModule options plus the container definition. The serviceName
# is set to "podman-<name>" automatically.
#
# Required args:
# config — the NixOS config attrset (pass through from the module args)
# name — service name (used for the container name and option path)
# image — OCI image reference string
# internalPort — port the container listens on internally
#
# Optional args:
# description — human-readable description (defaults to name)
# options — extra mkModule options attrset
# volumes — extra volume strings (in addition to none)
# environment — extra environment variables (merged with PUID/PGID/TZ)
# environmentFiles — list of paths to env-files (e.g. sops template paths)
# extraOptions — list of extra --opt strings passed to the container runtime
# devices — list of device mappings
# extraConfig — extra NixOS config merged into moduleConfig
mkContainerService =
{
config,
name,
image,
internalPort,
description ? name,
options ? { },
volumes ? [ ],
environment ? { },
environmentFiles ? [ ],
extraOptions ? [ ],
devices ? [ ],
extraConfig ? { },
}:
let
cfg = config.${namespace}.services.${name};
in
mkModule {
inherit
config
name
description
options
;
serviceName = "podman-${name}";
moduleConfig = lib.recursiveUpdate {
virtualisation.oci-containers.containers.${name} = {
autoStart = true;
inherit
image
volumes
environmentFiles
extraOptions
devices
;
ports = [ "${toString cfg.port}:${toString internalPort}" ];
environment = {
PUID = cfg.puid;
PGID = cfg.pgid;
TZ = cfg.timeZone;
}
// environment;
};
} extraConfig;
};
# Generates a sops secrets block + a sops template env-file in a single call.
#
# secrets — attrset of sops secret keys → extra attrs (e.g. owner/group).
# The sopsFile is set automatically to nas-secrets.yaml unless
# overridden per-secret via { sopsFile = ...; }.
# name — template file name, e.g. "glance.env"
# content — the template body string (use config.sops.placeholder."key")
# restartUnit — systemd unit to restart when the secret changes
# owner, group, mode — file ownership/permissions (defaults match NAS convention)
# sopsFile — default sops file for all secrets (can be overridden per-secret)
mkSopsEnvFile =
{
secrets,
name,
content,
restartUnit,
owner ? "nix-apps",
group ? "jallen-nas",
mode ? "660",
sopsFile ? (lib.snowfall.fs.get-file "secrets/nas-secrets.yaml"),
}:
{
sops.secrets = mapAttrs (_key: extra: { inherit sopsFile; } // extra) secrets;
sops.templates.${name} = {
inherit
mode
owner
group
content
;
restartUnits = [ restartUnit ];
};
};
# ---------------------------------------------------------------------------
# Home Manager module helper
# ---------------------------------------------------------------------------
# Create a Home Manager module with a standard enable option and optional
# extra options, gating all config behind `cfg.enable`.
#
# domain — option namespace domain, e.g. "programs" or "desktop"
# name — module name, e.g. "btop"
# description — text for mkEnableOption (defaults to name)
# options — attrset of extra options merged into the submodule
# config — the NixOS/HM config attrset passed through from module args
# moduleConfig — the Home Manager config body (already gated behind cfg.enable)
mkHomeModule =
{
config,
domain,
name,
description ? name,
options ? { },
moduleConfig,
}:
let
cfg = config.${namespace}.${domain}.${name};
in
{ lib, ... }:
{
options.${namespace}.${domain}.${name} = lib.mkOption {
type = lib.types.submodule {
options = {
enable = lib.mkEnableOption description;
}
// options;
};
default = { };
};
config = lib.mkIf cfg.enable moduleConfig;
};
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Option creation helpers # Option creation helpers
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View File

@@ -70,7 +70,9 @@ in
}; };
programs = { programs = {
nix-index-database.comma = enabled; # nix-index-database is not available in all home configs (e.g. iso-minimal
# standalone homes don't load the nix-index-database HM module).
# Set it per-host in homes that explicitly load the module.
btop = { btop = {
enable = lib.mkDefault true; enable = lib.mkDefault true;
package = pkgs.btop; package = pkgs.btop;

View File

@@ -4,13 +4,13 @@
namespace, namespace,
... ...
}: }:
with lib;
let
cfg = config.${namespace}.programs.btop;
in
{ {
imports = [ ./options.nix ]; imports = [
config = mkIf cfg.enable { (lib.${namespace}.mkHomeModule {
inherit config;
domain = "programs";
name = "btop";
moduleConfig = {
programs.btop = { programs.btop = {
enable = true; enable = true;
settings = { settings = {
@@ -79,4 +79,6 @@ in
}; };
}; };
}; };
})
];
} }

View File

@@ -1,7 +0,0 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.programs.btop = {
enable = mkEnableOption "enable btop";
};
}

View File

@@ -1,21 +1,19 @@
{ {
lib,
config, config,
lib,
namespace, namespace,
... ...
}: }:
with lib;
let
cfg = config.${namespace}.programs.kitty;
in
{ {
imports = [ ./options.nix ]; imports = [
(lib.${namespace}.mkHomeModule {
config = mkIf cfg.enable { inherit config;
domain = "programs";
name = "kitty";
moduleConfig = {
programs.kitty = { programs.kitty = {
enable = true; enable = true;
shellIntegration.enableZshIntegration = true; shellIntegration.enableZshIntegration = true;
settings = { settings = {
bold_font = "auto"; bold_font = "auto";
italic_font = "auto"; italic_font = "auto";
@@ -27,4 +25,6 @@ in
}; };
}; };
}; };
})
];
} }

View File

@@ -1,7 +0,0 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.programs.kitty = {
enable = mkEnableOption "enable kitty terminal";
};
}

View File

@@ -4,17 +4,24 @@
namespace, namespace,
... ...
}: }:
with lib;
let
cfg = config.${namespace}.programs.mako;
in
{ {
imports = [ ./options.nix ]; imports = [
config = mkIf cfg.enable { (lib.${namespace}.mkHomeModule {
inherit config;
domain = "programs";
name = "mako";
options = {
fontName = lib.mkOption {
type = lib.types.str;
default = "DejaVu Sans";
description = "Font name for mako notifications.";
};
};
moduleConfig = {
services.mako = { services.mako = {
enable = true; enable = true;
settings = { settings = {
font = mkDefault cfg.fontName; font = lib.mkDefault config.${namespace}.programs.mako.fontName;
icons = true; icons = true;
ignore-timeout = true; ignore-timeout = true;
sort = "-time"; sort = "-time";
@@ -25,12 +32,9 @@ in
border-size = 1; border-size = 1;
max-icon-size = 64; max-icon-size = 64;
default-timeout = 5000; default-timeout = 5000;
# background-color = mkDefault config.lib.stylix.colors.base00;
# text-color = mkDefault config.lib.stylix.colors.base06;
# border-color = mkDefault config.lib.stylix.colors.base0F;
# progress-color = mkDefault "over ${config.lib.stylix.colors.base0C}";
}; };
}; };
}; };
})
];
} }

View File

@@ -1,12 +0,0 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.programs.mako = {
enable = mkEnableOption "enable mako";
fontName = mkOption {
type = types.str;
default = "DejaVu Sans";
};
};
}

View File

@@ -5,14 +5,13 @@
namespace, namespace,
... ...
}: }:
with lib;
let
cfg = config.${namespace}.programs.nwg-dock;
in
{ {
imports = [ ./options.nix ]; imports = [
(lib.${namespace}.mkHomeModule {
config = mkIf cfg.enable { inherit config;
domain = "programs";
name = "nwg-dock";
moduleConfig = {
home.packages = with pkgs; [ nwg-dock-hyprland ]; home.packages = with pkgs; [ nwg-dock-hyprland ];
home.file = { home.file = {
@@ -54,12 +53,10 @@ in
} }
#box { #box {
/* Define attributes of the box surrounding icons here */
padding: 10px padding: 10px
} }
#active { #active {
/* This is to underline the button representing the currently active window */
border-bottom: solid 1px; border-bottom: solid 1px;
border-color: rgba(255, 255, 255, 0.3) border-color: rgba(255, 255, 255, 0.3)
} }
@@ -99,12 +96,10 @@ in
} }
#box { #box {
/* Define attributes of the box surrounding icons here */
padding: 10px padding: 10px
} }
active { active {
/* This is to underline the button representing the currently active window */
border-bottom: solid 1px; border-bottom: solid 1px;
border-color: ${config.lib.stylix.colors.base0B}1a border-color: ${config.lib.stylix.colors.base0B}1a
} }
@@ -135,4 +130,6 @@ in
''; '';
}; };
}; };
})
];
} }

View File

@@ -1,7 +0,0 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.programs.nwg-dock = {
enable = mkEnableOption "enable nwg-dock";
};
}

View File

@@ -5,24 +5,21 @@
namespace, namespace,
... ...
}: }:
with lib;
let
cfg = config.${namespace}.programs.nwg-drawer;
in
{ {
imports = [ ./options.nix ]; imports = [
(lib.${namespace}.mkHomeModule {
config = mkIf cfg.enable { inherit config;
domain = "programs";
name = "nwg-drawer";
moduleConfig = {
home.packages = with pkgs; [ nwg-drawer ]; home.packages = with pkgs; [ nwg-drawer ];
home.file = { home.file.".config/nwg-drawer/drawer.css".text = ''
".config/nwg-drawer/drawer.css".text = ''
window { window {
background-color: ${config.lib.stylix.colors.base00}bf; background-color: ${config.lib.stylix.colors.base00}bf;
color: ${config.lib.stylix.colors.base05}00 color: ${config.lib.stylix.colors.base05}00
} }
/* search entry */
entry { entry {
background-color: ${config.lib.stylix.colors.base01}0f background-color: ${config.lib.stylix.colors.base01}0f
} }
@@ -36,7 +33,6 @@ in
background-color: ${config.lib.stylix.colors.base0F}1a background-color: ${config.lib.stylix.colors.base0F}1a
} }
/* in case you wanted to give category buttons a different look */
#category-button { #category-button {
margin: 0 10px 0 10px margin: 0 10px 0 10px
} }
@@ -53,5 +49,6 @@ in
} }
''; '';
}; };
}; })
];
} }

View File

@@ -1,7 +0,0 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.programs.nwg-drawer = {
enable = mkEnableOption "enable nwg-drawer";
};
}

View File

@@ -4,52 +4,22 @@
namespace, namespace,
... ...
}: }:
with lib;
let
cfg = config.${namespace}.programs.wlogout;
in
{ {
imports = [ ./options.nix ]; imports = [
config = mkIf cfg.enable { (lib.${namespace}.mkHomeModule {
inherit config;
domain = "programs";
name = "wlogout";
moduleConfig = {
programs.wlogout = { programs.wlogout = {
enable = false; enable = false;
layout = { layout = {
lock = { lock = { label = "lock"; action = "hyprlock --immediate"; text = "Lock"; keybind = "l"; };
label = "lock"; hibernate = { label = "hibernate"; action = "systemctl hibernate"; text = "Hibernate"; keybind = "h"; };
action = "hyprlock --immediate"; logout = { label = "logout"; action = "sleep 1; hyprctl dispatch exit"; text = "Logout"; keybind = "e"; };
text = "Lock"; shutdown = { label = "shutdown"; action = "systemctl poweroff"; text = "Shutdown"; keybind = "s"; };
keybind = "l"; suspend = { label = "suspend"; action = "systemctl suspend"; text = "Suspend"; keybind = "u"; };
}; reboot = { label = "reboot"; action = "reboot"; text = "Reboot"; keybind = "r"; };
hibernate = {
label = "hibernate";
action = "systemctl hibernate";
text = "Hibernate";
keybind = "h";
};
logout = {
label = "logout";
action = "sleep 1; hyprctl dispatch exit";
text = "Logout";
keybind = "e";
};
shutdown = {
label = "shutdown";
action = "systemctl poweroff";
text = "Shutdown";
keybind = "s";
};
suspend = {
label = "suspend";
action = "systemctl suspend";
text = "Suspend";
keybind = "u";
};
reboot = {
label = "reboot";
action = "reboot";
text = "Reboot";
keybind = "r";
};
}; };
style = '' style = ''
* { * {
@@ -79,30 +49,15 @@ in
outline-style: none; outline-style: none;
} }
#lock { #lock { background-image: image(url("icons/lock.png")); }
background-image: image(url("icons/lock.png")); #logout { background-image: image(url("icons/logout.png")); }
} #suspend { background-image: image(url("icons/suspend.png")); }
#hibernate { background-image: image(url("icons/hibernate.png")); }
#logout { #shutdown { background-image: image(url("icons/shutdown.png")); }
background-image: image(url("icons/logout.png")); #reboot { background-image: image(url("icons/reboot.png")); }
}
#suspend {
background-image: image(url("icons/suspend.png"));
}
#hibernate {
background-image: image(url("icons/hibernate.png"));
}
#shutdown {
background-image: image(url("icons/shutdown.png"));
}
#reboot {
background-image: image(url("icons/reboot.png"));
}
''; '';
}; };
}; };
})
];
} }

View File

@@ -1,7 +0,0 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.programs.wlogout = {
enable = mkEnableOption "enable wlogout";
};
}

View File

@@ -4,23 +4,28 @@
namespace, namespace,
... ...
}: }:
with lib;
let
cfg = config.${namespace}.programs.wofi;
in
{ {
imports = [ ./options.nix ]; imports = [
(lib.${namespace}.mkHomeModule {
config = mkIf cfg.enable { inherit config;
domain = "programs";
name = "wofi";
options = {
fontName = lib.mkOption {
type = lib.types.str;
default = "DejaVu Sans";
description = "Font name for wofi.";
};
};
moduleConfig = {
programs.wofi = { programs.wofi = {
enable = true; enable = true;
style = '' style = ''
* { * {
font-family: "${cfg.fontName}", monospace; font-family: "${config.${namespace}.programs.wofi.fontName}", monospace;
font-size: 14px; font-size: 14px;
} }
/* Window */
window { window {
margin: 0px; margin: 0px;
padding: 10px; padding: 10px;
@@ -29,7 +34,6 @@ in
background-color: ${config.lib.stylix.colors.base00}; background-color: ${config.lib.stylix.colors.base00};
} }
/* Inner Box */
#inner-box { #inner-box {
margin: 5px; margin: 5px;
padding: 10px; padding: 10px;
@@ -37,7 +41,6 @@ in
background-color: ${config.lib.stylix.colors.base00}; background-color: ${config.lib.stylix.colors.base00};
} }
/* Outer Box */
#outer-box { #outer-box {
margin: 5px; margin: 5px;
padding: 10px; padding: 10px;
@@ -45,7 +48,6 @@ in
background-color: ${config.lib.stylix.colors.base00}; background-color: ${config.lib.stylix.colors.base00};
} }
/* Scroll */
#scroll { #scroll {
margin: 0px; margin: 0px;
padding: 10px; padding: 10px;
@@ -53,7 +55,6 @@ in
background-color: ${config.lib.stylix.colors.base00}; background-color: ${config.lib.stylix.colors.base00};
} }
/* Input */
#input { #input {
margin: 5px 20px; margin: 5px 20px;
padding: 10px; padding: 10px;
@@ -72,7 +73,6 @@ in
outline: 4px solid ${config.lib.stylix.colors.base08}!important; outline: 4px solid ${config.lib.stylix.colors.base08}!important;
} }
/* Text */
#text { #text {
margin: 5px; margin: 5px;
border: none; border: none;
@@ -88,7 +88,6 @@ in
color: ${config.lib.stylix.colors.base0E}; color: ${config.lib.stylix.colors.base0E};
} }
/* Selected Entry */
#entry:selected { #entry:selected {
border: 0.11em solid ${config.lib.stylix.colors.base0E}; border: 0.11em solid ${config.lib.stylix.colors.base0E};
} }
@@ -103,4 +102,6 @@ in
''; '';
}; };
}; };
})
];
} }

View File

@@ -1,12 +0,0 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.programs.wofi = {
enable = mkEnableOption "enable wofi";
fontName = mkOption {
type = types.str;
default = "Deja Vu Sans";
};
};
}

View File

@@ -22,7 +22,7 @@ in
wayland = lib.mkDefault true; wayland = lib.mkDefault true;
}; };
gnome = { gnome = lib.mkOverride 90 {
at-spi2-core = disabled; at-spi2-core = disabled;
core-apps = enabled; core-apps = enabled;
core-developer-tools = disabled; core-developer-tools = disabled;

View File

@@ -144,10 +144,7 @@ in
# Configure WiFi profiles if any are defined # Configure WiFi profiles if any are defined
ensureProfiles = mkIf (cfg.networkmanager.profiles != { }) { ensureProfiles = mkIf (cfg.networkmanager.profiles != { }) {
environmentFiles = [ environmentFiles = lib.optional (config.sops.secrets ? wifi) config.sops.secrets.wifi.path;
config.sops.secrets.wifi.path
];
profiles = profiles; profiles = profiles;
}; };
}) })

View File

@@ -4,7 +4,6 @@
namespace, namespace,
... ...
}: }:
with lib;
# NOTE: AUTHENTIK_TOKEN for the RAC outpost is stored in sops. # NOTE: AUTHENTIK_TOKEN for the RAC outpost is stored in sops.
# Add jallen-nas/authentik-rac/token to secrets/nas-secrets.yaml and ensure # Add jallen-nas/authentik-rac/token to secrets/nas-secrets.yaml and ensure
# jallen-nas/sops.nix declares the "authentik-rac.env" template before deploying. # jallen-nas/sops.nix declares the "authentik-rac.env" template before deploying.
@@ -15,37 +14,31 @@ let
authentikConfig = lib.${namespace}.mkModule { authentikConfig = lib.${namespace}.mkModule {
inherit config name; inherit config name;
description = "authentik Service"; description = "authentik identity provider";
options = { }; options = { };
moduleConfig = { moduleConfig = {
services = { services.authentik = {
authentik = {
enable = true; enable = true;
environmentFile = cfg.environmentFile; environmentFile = cfg.environmentFile;
settings = { settings.port = cfg.port;
port = cfg.port;
};
};
}; };
}; };
}; };
# RAC outpost: uses podman but has a legacy container name "authenticRac"
# (different from the option name "authentikRac"), so we use mkModule directly.
authentikRacConfig = lib.${namespace}.mkModule { authentikRacConfig = lib.${namespace}.mkModule {
inherit config; inherit config;
name = "authentikRac"; name = "authentikRac";
serviceName = "podman-authenticRac"; serviceName = "podman-authenticRac";
description = "authentik_rac Service"; description = "authentik RAC outpost";
options = { }; options = { };
moduleConfig = { moduleConfig = {
virtualisation.oci-containers.containers."authenticRac" = { virtualisation.oci-containers.containers."authenticRac" = {
autoStart = true; autoStart = true;
image = "ghcr.io/goauthentik/rac"; image = "ghcr.io/goauthentik/rac";
ports = [ "${toString cfgRac.port}:4822" ]; ports = [ "${toString cfgRac.port}:4822" ];
volumes = [ volumes = [ "${cfg.configDir}/authentik-rac:/media" ];
"${cfg.configDir}/authentik-rac:/media"
];
# AUTHENTIK_TOKEN is injected via the sops template "authentik-rac.env"
# defined in systems/x86_64-linux/jallen-nas/sops.nix
environmentFiles = [ config.sops.templates."authentik-rac.env".path ]; environmentFiles = [ config.sops.templates."authentik-rac.env".path ];
environment = { environment = {
AUTHENTIK_HOST = "https://${name}.mjallen.dev"; AUTHENTIK_HOST = "https://${name}.mjallen.dev";

View File

@@ -4,40 +4,27 @@
namespace, namespace,
... ...
}: }:
with lib;
let let
cfg = config.${namespace}.services.booklore;
in
{
imports = [
(lib.${namespace}.mkContainerService {
inherit config;
name = "booklore"; name = "booklore";
cfg = config.${namespace}.services.${name};
bookloreConfig = lib.${namespace}.mkModule {
inherit config name;
serviceName = "podman-${name}";
description = "booklore";
options = { };
moduleConfig = {
virtualisation.oci-containers.containers.${name} = {
autoStart = true;
image = "booklore/booklore"; image = "booklore/booklore";
internalPort = 6060;
volumes = [ volumes = [
"${cfg.configDir}/booklore:/app/data" "${cfg.configDir}/booklore:/app/data"
"${cfg.configDir}/bookdrop:/bookdrop" "${cfg.configDir}/bookdrop:/bookdrop"
"${cfg.dataDir}/books:/books" "${cfg.dataDir}/books:/books"
]; ];
ports = [
"${toString cfg.port}:6060"
];
environment = { environment = {
DATABASE_URL = "jdbc:mariadb://10.0.1.3:3306/booklore"; DATABASE_URL = "jdbc:mariadb://10.0.1.3:3306/booklore";
DATABASE_USERNAME = "booklore"; DATABASE_USERNAME = "booklore";
# TODO: move DATABASE_PASSWORD to a sops secret
DATABASE_PASSWORD = "Lucifer008!"; DATABASE_PASSWORD = "Lucifer008!";
PUID = cfg.puid;
PGID = cfg.pgid;
TZ = cfg.timeZone;
}; };
}; })
}; ];
};
in
{
imports = [ bookloreConfig ];
} }

View File

@@ -4,41 +4,25 @@
namespace, namespace,
... ...
}: }:
with lib;
let let
cfg = config.${namespace}.services.dispatcharr;
in
{
imports = [
(lib.${namespace}.mkContainerService {
inherit config;
name = "dispatcharr"; name = "dispatcharr";
cfg = config.${namespace}.services.${name};
dispatcharrConfig = lib.${namespace}.mkModule {
inherit config name;
serviceName = "podman-${name}";
description = "dispatcharr podman container";
options = { };
moduleConfig = {
virtualisation.oci-containers.containers.${name} = {
autoStart = true;
image = "ghcr.io/dispatcharr/dispatcharr"; image = "ghcr.io/dispatcharr/dispatcharr";
internalPort = 9191;
extraOptions = [ "--device=/dev/dri" ]; extraOptions = [ "--device=/dev/dri" ];
volumes = [ volumes = [
"${cfg.configDir}/dispatcharr:/data" "${cfg.configDir}/dispatcharr:/data"
"${cfg.dataDir}/movies:/movies" "${cfg.dataDir}/movies:/movies"
"${cfg.dataDir}/tv:/tv" "${cfg.dataDir}/tv:/tv"
]; ];
ports = [
"${toString cfg.port}:9191"
];
environment = { environment = {
# DISPATCHARR_LOG_LEVEL = "DEBUG";
DISPATCHARR_ENV = "aio"; DISPATCHARR_ENV = "aio";
# DJANGO_SECRET_KEY = "123456";
# PUID = cfg.puid;
# PGID = cfg.pgid;
# TZ = cfg.timeZone;
}; };
}; })
}; ];
};
in
{
imports = [ dispatcharrConfig ];
} }

View File

@@ -4,32 +4,18 @@
namespace, namespace,
... ...
}: }:
with lib;
let let
name = "free-games-claimer"; cfg = config.${namespace}.services."free-games-claimer";
cfg = config.${namespace}.services.${name};
fgcConfig = lib.${namespace}.mkModule {
inherit config name;
serviceName = "podman-${name}";
description = "free-games-claimer";
options = { };
moduleConfig = {
virtualisation.oci-containers.containers."${name}" = {
autoStart = true;
image = "ghcr.io/vogler/free-games-claimer";
ports = [ "${toString cfg.port}:6080" ];
volumes = [ "${cfg.configDir}/free-games-claimer:/fgc/data" ];
environmentFiles = [ config.sops.templates."fgc.env".path ];
environment = {
PUID = cfg.puid;
PGID = cfg.pgid;
TZ = cfg.timeZone;
};
};
};
};
in in
{ {
imports = [ fgcConfig ]; imports = [
(lib.${namespace}.mkContainerService {
inherit config;
name = "free-games-claimer";
image = "ghcr.io/vogler/free-games-claimer";
internalPort = 6080;
volumes = [ "${cfg.configDir}/free-games-claimer:/fgc/data" ];
environmentFiles = [ config.sops.templates."fgc.env".path ];
})
];
} }

View File

@@ -19,27 +19,6 @@ let
}; };
}; };
moduleConfig = { moduleConfig = {
sops = {
secrets = {
"jallen-nas/glance/arr-username" = {
sopsFile = (lib.snowfall.fs.get-file "secrets/nas-secrets.yaml");
};
"jallen-nas/glance/arr-password" = {
sopsFile = (lib.snowfall.fs.get-file "secrets/nas-secrets.yaml");
};
};
templates = {
"glance.env" = {
mode = "660";
restartUnits = [ "glance.service" ];
content = ''
ARR_USER=${config.sops.placeholder."jallen-nas/glance/arr-username"}
ARR_PASS=${config.sops.placeholder."jallen-nas/glance/arr-password"}
'';
};
};
};
services.glance = { services.glance = {
enable = true; enable = true;
openFirewall = true; openFirewall = true;
@@ -301,5 +280,25 @@ let
}; };
in in
{ {
imports = [ glanceConfig ]; imports = [
glanceConfig
# Sops env-file for arr credentials (gated behind glance.enable)
{
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"}
'';
}
);
}
];
} }

View File

@@ -1,40 +1,23 @@
{ {
config,
lib, lib,
config,
namespace, namespace,
... ...
}: }:
with lib;
let let
cfg = config.${namespace}.services.lubelogger;
in
{
imports = [
(lib.${namespace}.mkContainerService {
inherit config;
name = "lubelogger"; name = "lubelogger";
cfg = config.${namespace}.services.${name};
lubeloggerConfig = lib.${namespace}.mkModule {
inherit config name;
serviceName = "podman-${name}";
description = "lubelogger";
options = { };
moduleConfig = {
virtualisation.oci-containers.containers.lubelogger = {
autoStart = true;
image = "ghcr.io/hargata/lubelogger"; image = "ghcr.io/hargata/lubelogger";
ports = [ "${toString cfg.port}:8080" ]; internalPort = 8080;
volumes = [ volumes = [
"${cfg.configDir}/lubelogger:/App/data" "${cfg.configDir}/lubelogger:/App/data"
"${cfg.configDir}/lubelogger/keys:/root/.aspnet/DataProtection-Keys" "${cfg.configDir}/lubelogger/keys:/root/.aspnet/DataProtection-Keys"
]; ];
# environmentFiles = [ })
# "${cfg.configDir}/lubelogger/lubelogger.env" ];
# ];
environment = {
PUID = toString config.users.users.nix-apps.uid;
PGID = toString config.users.groups.jallen-nas.gid;
TZ = "America/Chicago";
};
};
};
};
in
{
imports = [ lubeloggerConfig ];
} }

View File

@@ -4,21 +4,16 @@
namespace, namespace,
... ...
}: }:
with lib;
let let
cfg = config.${namespace}.services.manyfold;
in
{
imports = [
(lib.${namespace}.mkContainerService {
inherit config;
name = "manyfold"; name = "manyfold";
cfg = config.${namespace}.services.${name};
manyfoldConfig = lib.${namespace}.mkModule {
inherit config name;
serviceName = "podman-${name}";
description = "manyfold";
options = { };
moduleConfig = {
virtualisation.oci-containers.containers."${name}" = {
autoStart = true;
image = "ghcr.io/manyfold3d/manyfold-solo"; image = "ghcr.io/manyfold3d/manyfold-solo";
ports = [ "${toString cfg.port}:3214" ]; internalPort = 3214;
extraOptions = [ extraOptions = [
"--cap-drop=ALL" "--cap-drop=ALL"
"--cap-add=CHOWN" "--cap-add=CHOWN"
@@ -31,16 +26,7 @@ let
"${cfg.configDir}/manyfold:/config" "${cfg.configDir}/manyfold:/config"
"${cfg.dataDir}/documents/3d-models:/libraries" "${cfg.dataDir}/documents/3d-models:/libraries"
]; ];
environment = {
PUID = cfg.puid;
PGID = cfg.pgid;
TZ = cfg.timeZone;
};
environmentFiles = [ config.sops.secrets."jallen-nas/manyfold/secretkeybase".path ]; environmentFiles = [ config.sops.secrets."jallen-nas/manyfold/secretkeybase".path ];
}; })
}; ];
};
in
{
imports = [ manyfoldConfig ];
} }

View File

@@ -1,54 +1,39 @@
{ {
config,
lib, lib,
config,
namespace, namespace,
... ...
}: }:
with lib;
let let
inherit (lib.${namespace}) mkOpt; inherit (lib.${namespace}) mkOpt mkContainerService;
cfg = config.${namespace}.services.netbootxyz;
in
{
imports = [
(mkContainerService {
inherit config;
name = "netbootxyz"; name = "netbootxyz";
cfg = config.${namespace}.services.${name};
netbootxyzConfig = lib.${namespace}.mkModule {
inherit config name;
description = "netbootxyz";
options = {
assetPort = mkOpt types.port 4001 "NGINX server for hosting assets.";
tftpPort = mkOpt types.port 69 "HTTPS port for netbootxyz";
};
moduleConfig = {
# Open firewall for netbootxyz if enabled
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [
cfg.assetPort
cfg.tftpPort
];
allowedUDPPorts = [
cfg.assetPort
cfg.tftpPort
];
};
virtualisation.oci-containers = {
containers.netbootxyz = {
autoStart = true;
image = "ghcr.io/netbootxyz/netbootxyz:latest"; image = "ghcr.io/netbootxyz/netbootxyz:latest";
ports = [ internalPort = 3000;
"${toString cfg.port}:3000" options = {
"${toString cfg.assetPort}:80" assetPort = mkOpt lib.types.port 4001 "NGINX port for hosting assets";
"${toString cfg.tftpPort}:69" tftpPort = mkOpt lib.types.port 69 "TFTP port";
]; };
volumes = [ volumes = [
"${cfg.configDir}/netbootxyz:/config" "${cfg.configDir}/netbootxyz:/config"
"${cfg.dataDir}/isos:/assets" "${cfg.dataDir}/isos:/assets"
]; ];
extraConfig = {
networking.firewall = lib.mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.assetPort cfg.tftpPort ];
allowedUDPPorts = [ cfg.assetPort cfg.tftpPort ];
}; };
virtualisation.oci-containers.containers.netbootxyz.ports = lib.mkForce [
"${toString cfg.port}:3000"
"${toString cfg.assetPort}:80"
"${toString cfg.tftpPort}:69"
];
}; };
}; })
}; ];
in
{
imports = [ netbootxyzConfig ];
} }

View File

@@ -4,37 +4,27 @@
namespace, namespace,
... ...
}: }:
with lib;
let let
inherit (lib.${namespace}) mkOpt; inherit (lib.${namespace}) mkOpt mkContainerService;
cfg = config.${namespace}.services."orca-slicer";
in
{
imports = [
(mkContainerService {
inherit config;
name = "orca-slicer"; name = "orca-slicer";
cfg = config.${namespace}.services.${name};
orcaConfig = lib.${namespace}.mkModule {
inherit config name;
serviceName = "podman-${name}";
description = "orca slicer web ui";
options = {
httpsPort = mkOpt types.int 443 "HTTPS port";
};
moduleConfig = {
virtualisation.oci-containers.containers."${name}" = {
autoStart = true;
image = "linuxserver/orcaslicer"; image = "linuxserver/orcaslicer";
ports = [ internalPort = 3000;
options = {
httpsPort = mkOpt lib.types.int 443 "HTTPS port";
};
extraConfig = {
virtualisation.oci-containers.containers."orca-slicer".ports = lib.mkForce [
"${toString cfg.port}:3000" "${toString cfg.port}:3000"
"${toString cfg.httpsPort}:3001" "${toString cfg.httpsPort}:3001"
]; ];
};
volumes = [ "${cfg.configDir}/orca-slicer:/config" ]; volumes = [ "${cfg.configDir}/orca-slicer:/config" ];
environment = { })
PUID = cfg.puid; ];
PGID = cfg.pgid;
TZ = cfg.timeZone;
};
};
};
};
in
{
imports = [ orcaConfig ];
} }

View File

@@ -4,118 +4,74 @@
namespace, namespace,
... ...
}: }:
with lib;
let let
name = "sparky-fitness-server"; inherit (lib.${namespace}) mkContainerService;
cfg = config.${namespace}.services.${name};
sparky-fitness-server = lib.${namespace}.mkModule { serverName = "sparky-fitness-server";
inherit config name; frontendName = "sparky-fitness";
serviceName = "podman-${name}"; dbName = "sparky-fitness-db";
description = "sparky-fitness-server";
options = { }; serverCfg = config.${namespace}.services.${serverName};
moduleConfig = { frontendCfg = config.${namespace}.services.${frontendName};
virtualisation.oci-containers.containers.${name} = { dbCfg = config.${namespace}.services.${dbName};
autoStart = true; in
{
imports = [
(mkContainerService {
inherit config;
name = serverName;
image = "codewithcj/sparkyfitness_server"; image = "codewithcj/sparkyfitness_server";
ports = [ "${toString cfg.port}:3010" ]; internalPort = 3010;
volumes = [ volumes = [
"${cfg.configDir}/sparky-fitness/server/backup:/app/SparkyFitnessServer/backup" "${serverCfg.configDir}/sparky-fitness/server/backup:/app/SparkyFitnessServer/backup"
"${cfg.configDir}/sparky-fitness/server/uploads:/app/SparkyFitnessServer/uploads" "${serverCfg.configDir}/sparky-fitness/server/uploads:/app/SparkyFitnessServer/uploads"
]; ];
# environmentFiles = [
# "${cfg.configDir}/lubelogger/lubelogger.env"
# ];
environment = { environment = {
SPARKY_FITNESS_LOG_LEVEL = "0"; SPARKY_FITNESS_LOG_LEVEL = "0";
ALLOW_PRIVATE_NETWORK_CORS = "false"; ALLOW_PRIVATE_NETWORK_CORS = "false";
SPARKY_FITNESS_EXTRA_TRUSTED_ORIGINS = ""; SPARKY_FITNESS_EXTRA_TRUSTED_ORIGINS = "";
SPARKY_FITNESS_DB_USER = "sparkyfitness"; SPARKY_FITNESS_DB_USER = "sparkyfitness";
SPARKY_FITNESS_DB_HOST = "10.0.1.3"; # Use the service name 'sparkyfitness-db' for inter-container communication SPARKY_FITNESS_DB_HOST = "10.0.1.3";
SPARKY_FITNESS_DB_NAME = "sparkyfitness"; SPARKY_FITNESS_DB_NAME = "sparkyfitness";
# TODO: move DB password and secrets to sops
SPARKY_FITNESS_DB_PASSWORD = "sparkyfitness"; SPARKY_FITNESS_DB_PASSWORD = "sparkyfitness";
SPARKY_FITNESS_APP_DB_USER = "sparkyfitness"; SPARKY_FITNESS_APP_DB_USER = "sparkyfitness";
SPARKY_FITNESS_APP_DB_PASSWORD = "sparkyfitness"; SPARKY_FITNESS_APP_DB_PASSWORD = "sparkyfitness";
SPARKY_FITNESS_DB_PORT = "${toString dbCfg.port}"; SPARKY_FITNESS_DB_PORT = "${toString dbCfg.port}";
SPARKY_FITNESS_API_ENCRYPTION_KEY = "088ab2c6487ca1048c1fe74a4d8bd906e88db56953406769426b615d6df2407b"; SPARKY_FITNESS_API_ENCRYPTION_KEY = "088ab2c6487ca1048c1fe74a4d8bd906e88db56953406769426b615d6df2407b";
# Uncomment the line below and comment the line above to use a file-based secret
# SPARKY_FITNESS_API_ENCRYPTION_KEY_FILE: /run/secrets/sparkyfitness_api_key
BETTER_AUTH_SECRET = "a0304bda5a9efd0d92595c8d46526e33d58f436408f6b70ea37c2b84308d9abe"; BETTER_AUTH_SECRET = "a0304bda5a9efd0d92595c8d46526e33d58f436408f6b70ea37c2b84308d9abe";
# Uncomment the line below and comment the line above to use a file-based secret
# BETTER_AUTH_SECRET_FILE: /run/secrets/sparkyfitness_better_auth_secret
SPARKY_FITNESS_FRONTEND_URL = "http://10.0.1.3:${toString frontendCfg.port}"; SPARKY_FITNESS_FRONTEND_URL = "http://10.0.1.3:${toString frontendCfg.port}";
SPARKY_FITNESS_DISABLE_SIGNUP = "false"; SPARKY_FITNESS_DISABLE_SIGNUP = "false";
SPARKY_FITNESS_ADMIN_EMAIL = "jalle008@proton.me"; #User with this email can access the admin panel SPARKY_FITNESS_ADMIN_EMAIL = "jalle008@proton.me";
# SPARKY_FITNESS_EMAIL_HOST = "${SPARKY_FITNESS_EMAIL_HOST}";
# SPARKY_FITNESS_EMAIL_PORT = "${SPARKY_FITNESS_EMAIL_PORT}";
# SPARKY_FITNESS_EMAIL_SECURE = "${SPARKY_FITNESS_EMAIL_SECURE}";
# SPARKY_FITNESS_EMAIL_USER = "${SPARKY_FITNESS_EMAIL_USER}";
# SPARKY_FITNESS_EMAIL_PASS = "${SPARKY_FITNESS_EMAIL_PASS}";
# SPARKY_FITNESS_EMAIL_FROM = "${SPARKY_FITNESS_EMAIL_FROM}";
PUID = toString config.users.users.nix-apps.uid;
PGID = toString config.users.groups.jallen-nas.gid;
TZ = "America/Chicago";
};
};
};
}; };
})
fontendName = "sparky-fitness"; (mkContainerService {
frontendCfg = config.${namespace}.services.${fontendName};
sparky-fitness-frontend = lib.${namespace}.mkModule {
inherit config; inherit config;
name = fontendName; name = frontendName;
serviceName = "podman-${fontendName}";
description = "sparky-fitness";
options = { };
moduleConfig = {
virtualisation.oci-containers.containers.${fontendName} = {
autoStart = true;
image = "codewithcj/sparkyfitness"; image = "codewithcj/sparkyfitness";
ports = [ "${toString frontendCfg.port}:80" ]; internalPort = 80;
environment = { environment = {
SPARKY_FITNESS_FRONTEND_URL = "http://10.0.1.3:${toString frontendCfg.port}"; SPARKY_FITNESS_FRONTEND_URL = "http://10.0.1.3:${toString frontendCfg.port}";
SPARKY_FITNESS_SERVER_HOST = "10.0.1.3"; SPARKY_FITNESS_SERVER_HOST = "10.0.1.3";
SPARKY_FITNESS_SERVER_PORT = "${toString cfg.port}"; SPARKY_FITNESS_SERVER_PORT = "${toString serverCfg.port}";
PUID = toString config.users.users.nix-apps.uid;
PGID = toString config.users.groups.jallen-nas.gid;
TZ = "America/Chicago";
};
};
};
}; };
})
dbName = "sparky-fitness-db"; (mkContainerService {
dbCfg = config.${namespace}.services.${dbName};
sparky-fitness-db = lib.${namespace}.mkModule {
inherit config; inherit config;
name = dbName; name = dbName;
serviceName = "podman-${dbName}";
description = "sparky-fitness-db";
options = { };
moduleConfig = {
virtualisation.oci-containers.containers.${dbName} = {
autoStart = true;
image = "postgres:15-alpine"; image = "postgres:15-alpine";
ports = [ "${toString dbCfg.port}:5432" ]; internalPort = 5432;
volumes = [ volumes = [
"${dbCfg.configDir}/sparky-fitness/db:/var/lib/postgresql/data" "${dbCfg.configDir}/sparky-fitness/db:/var/lib/postgresql/data"
]; ];
environment = { environment = {
POSTGRES_DB = "sparkyfitness-db"; POSTGRES_DB = "sparkyfitness-db";
POSTGRES_USER = "sparkyfitness"; POSTGRES_USER = "sparkyfitness";
# TODO: move POSTGRES_PASSWORD to sops
POSTGRES_PASSWORD = "sparkyfitness"; POSTGRES_PASSWORD = "sparkyfitness";
PUID = toString config.users.users.nix-apps.uid;
PGID = toString config.users.groups.jallen-nas.gid;
TZ = "America/Chicago";
}; };
}; })
}; ];
};
in
{
imports = [ sparky-fitness-server sparky-fitness-frontend sparky-fitness-db ];
} }

View File

@@ -4,22 +4,20 @@
namespace, namespace,
... ...
}: }:
with lib;
let let
inherit (lib.${namespace}) mkOpt; inherit (lib.${namespace}) mkOpt mkContainerService;
cfg = config.${namespace}.services.tdarr;
in
{
imports = [
(mkContainerService {
inherit config;
name = "tdarr"; name = "tdarr";
cfg = config.${namespace}.services.${name};
tdarrConfig = lib.${namespace}.mkModule {
inherit config name;
description = "tdarr";
options = {
serverPort = mkOpt types.str "8266" "node port";
};
moduleConfig = {
virtualisation.oci-containers.containers.${name} = {
autoStart = true;
image = "ghcr.io/haveagitgat/tdarr"; image = "ghcr.io/haveagitgat/tdarr";
internalPort = 8265;
options = {
serverPort = mkOpt lib.types.str "8266" "Tdarr node server port";
};
extraOptions = [ "--device=nvidia.com/gpu=0" ]; extraOptions = [ "--device=nvidia.com/gpu=0" ];
volumes = [ volumes = [
"${cfg.configDir}/tdarr/config:/app/configs" "${cfg.configDir}/tdarr/config:/app/configs"
@@ -29,10 +27,6 @@ let
"${cfg.dataDir}/movies:/data/movies" "${cfg.dataDir}/movies:/data/movies"
"${cfg.dataDir}/tv:/data/tv" "${cfg.dataDir}/tv:/data/tv"
]; ];
ports = [
"${cfg.serverPort}:8266"
"${cfg.port}:8265"
];
environment = { environment = {
serverPort = "8266"; serverPort = "8266";
webUIPort = "8265"; webUIPort = "8265";
@@ -42,14 +36,13 @@ let
nodeName = "tdarr node"; nodeName = "tdarr node";
NVIDIA_VISIBLE_DEVICES = "all"; NVIDIA_VISIBLE_DEVICES = "all";
NVIDIA_DRIVER_CAPABILITIES = "all"; NVIDIA_DRIVER_CAPABILITIES = "all";
PUID = cfg.puid;
PGID = cfg.pgid;
TZ = cfg.timeZone;
}; };
extraConfig = {
virtualisation.oci-containers.containers.tdarr.ports = lib.mkForce [
"${cfg.serverPort}:8266"
"${toString cfg.port}:8265"
];
}; };
}; })
}; ];
in
{
imports = [ tdarrConfig ];
} }

View File

@@ -4,66 +4,44 @@
namespace, namespace,
... ...
}: }:
with lib;
let let
name = "termix"; cfg = config.${namespace}.services.termix;
cfg = config.${namespace}.services.${name}; inherit (lib.${namespace}) mkSopsEnvFile mkContainerService;
in
{
imports = [
./guacd.nix
termixConfig = lib.${namespace}.mkModule { # Sops env-file for OIDC credentials
inherit config name; {
serviceName = "podman-${name}"; config = lib.mkIf cfg.enable (mkSopsEnvFile {
description = "termix"; name = "termix.env";
options = { }; restartUnit = "podman-termix.service";
moduleConfig = {
sops = {
secrets = { secrets = {
"jallen-nas/termix/client-id" = { "jallen-nas/termix/client-id" = { };
sopsFile = (lib.snowfall.fs.get-file "secrets/nas-secrets.yaml"); "jallen-nas/termix/client-secret" = { };
}; };
"jallen-nas/termix/client-secret" = {
sopsFile = (lib.snowfall.fs.get-file "secrets/nas-secrets.yaml");
};
};
templates = {
"termix.env" = {
mode = "660";
owner = "nix-apps";
group = "jallen-nas";
restartUnits = [ "podman-termix.service" ];
content = '' content = ''
OIDC_CLIENT_ID=${config.sops.placeholder."jallen-nas/termix/client-id"} OIDC_CLIENT_ID=${config.sops.placeholder."jallen-nas/termix/client-id"}
OIDC_CLIENT_SECRET=${config.sops.placeholder."jallen-nas/termix/client-secret"} OIDC_CLIENT_SECRET=${config.sops.placeholder."jallen-nas/termix/client-secret"}
''; '';
}; });
}; }
};
virtualisation.oci-containers.containers.${name} = { (mkContainerService {
autoStart = true; inherit config;
name = "termix";
image = "ghcr.io/lukegus/termix"; image = "ghcr.io/lukegus/termix";
volumes = [ internalPort = 8080;
"${cfg.configDir}/termix:/app/data" volumes = [ "${cfg.configDir}/termix:/app/data" ];
]; environmentFiles = [ config.sops.templates."termix.env".path ];
ports = [
"${toString cfg.port}:8080"
];
environment = { environment = {
OIDC_ISSUER_URL = "https://authentik.mjallen.dev/application/o/termix/"; OIDC_ISSUER_URL = "https://authentik.mjallen.dev/application/o/termix/";
OIDC_AUTHORIZATION_URL = "https://authentik.mjallen.dev/application/o/authorize/"; OIDC_AUTHORIZATION_URL = "https://authentik.mjallen.dev/application/o/authorize/";
OIDC_TOKEN_URL = "https://authentik.mjallen.dev/application/o/token/"; OIDC_TOKEN_URL = "https://authentik.mjallen.dev/application/o/token/";
OIDC_FORCE_HTTPS = "true"; OIDC_FORCE_HTTPS = "true";
GUACD_HOST = "10.0.1.3"; GUACD_HOST = "10.0.1.3";
PUID = cfg.puid;
PGID = cfg.pgid;
TZ = cfg.timeZone;
}; };
}; })
};
};
in
{
imports = [
./guacd.nix
termixConfig
]; ];
} }

View File

@@ -4,32 +4,13 @@
namespace, namespace,
... ...
}: }:
with lib;
let
name = "guacd";
cfg = config.${namespace}.services.${name};
guacdConfig = lib.${namespace}.mkModule {
inherit config name;
serviceName = "podman-${name}";
description = "guacd";
options = { };
moduleConfig = {
virtualisation.oci-containers.containers.${name} = {
autoStart = true;
image = "guacamole/guacd";
ports = [
"${toString cfg.port}:4822"
];
environment = {
PUID = cfg.puid;
PGID = cfg.pgid;
TZ = cfg.timeZone;
};
};
};
};
in
{ {
imports = [ guacdConfig ]; imports = [
(lib.${namespace}.mkContainerService {
inherit config;
name = "guacd";
image = "guacamole/guacd";
internalPort = 4822;
})
];
} }

View File

@@ -4,20 +4,16 @@
namespace, namespace,
... ...
}: }:
with lib;
let let
cfg = config.${namespace}.services.tunarr;
in
{
imports = [
(lib.${namespace}.mkContainerService {
inherit config;
name = "tunarr"; name = "tunarr";
cfg = config.${namespace}.services.${name};
tunarrConfig = lib.${namespace}.mkModule {
inherit config name;
serviceName = "podman-${name}";
description = "tunarr";
options = { };
moduleConfig = {
virtualisation.oci-containers.containers.${name} = {
autoStart = true;
image = "ghcr.io/chrisbenincasa/tunarr"; image = "ghcr.io/chrisbenincasa/tunarr";
internalPort = 8000;
extraOptions = [ "--device=/dev/dri" ]; extraOptions = [ "--device=/dev/dri" ];
volumes = [ volumes = [
"${cfg.configDir}/tunarr:/config/tunarr" "${cfg.configDir}/tunarr:/config/tunarr"
@@ -26,18 +22,6 @@ let
"${cfg.dataDir}/tv:/libraries/tv" "${cfg.dataDir}/tv:/libraries/tv"
"${cfg.configDir}/transcode:/transcode" "${cfg.configDir}/transcode:/transcode"
]; ];
ports = [ })
"${toString cfg.port}:8000"
]; ];
environment = {
PUID = cfg.puid;
PGID = cfg.pgid;
TZ = cfg.timeZone;
};
};
};
};
in
{
imports = [ tunarrConfig ];
} }

View File

@@ -4,41 +4,23 @@
namespace, namespace,
... ...
}: }:
with lib;
let let
cfg = config.${namespace}.services.unmanic;
in
{
imports = [
(lib.${namespace}.mkContainerService {
inherit config;
name = "unmanic"; name = "unmanic";
cfg = config.${namespace}.services.${name};
unmanicConfig = lib.${namespace}.mkModule {
inherit config name;
serviceName = "podman-${name}";
description = "unmanic";
options = { };
moduleConfig = {
virtualisation.oci-containers.containers.${name} = {
autoStart = true;
image = "josh5/unmanic"; image = "josh5/unmanic";
devices = [ internalPort = 8888;
"/dev/dri:/dev/dri" devices = [ "/dev/dri:/dev/dri" ];
];
volumes = [ volumes = [
"${cfg.configDir}/unmanic:/config" "${cfg.configDir}/unmanic:/config"
"${cfg.dataDir}/movies:/library/movies" "${cfg.dataDir}/movies:/library/movies"
"${cfg.dataDir}/tv:/library/tv" "${cfg.dataDir}/tv:/library/tv"
"${cfg.configDir}/unmanic/transcode:/tmp/unmanic" "${cfg.configDir}/unmanic/transcode:/tmp/unmanic"
]; ];
ports = [ })
"${toString cfg.port}:8888"
]; ];
environment = {
PUID = cfg.puid;
PGID = cfg.pgid;
TZ = cfg.timeZone;
};
};
};
};
in
{
imports = [ unmanicConfig ];
} }

View File

@@ -4,20 +4,28 @@
namespace, namespace,
... ...
}: }:
with lib;
let let
cfg = config.${namespace}.services.your_spotify; inherit (lib.${namespace}) mkOpt mkModule;
name = "your-spotify";
cfg = config.${namespace}.services.${name};
in in
{ {
imports = [ ./options.nix ]; imports = [
(mkModule {
config = mkIf cfg.enable { inherit config name;
description = "Your Spotify self-hosted Spotify stats";
virtualisation.oci-containers.containers."${cfg.name}-server" = { options = {
serverPort = mkOpt lib.types.int 7777 "Port for the API server container";
webPort = mkOpt lib.types.int 7778 "Port for the web client container";
imageServer = mkOpt lib.types.str "yooooomi/your_spotify_server" "Server OCI image";
imageWeb = mkOpt lib.types.str "yooooomi/your_spotify_client" "Web client OCI image";
};
moduleConfig = {
virtualisation.oci-containers.containers."${name}-server" = {
autoStart = true; autoStart = true;
image = cfg.imageServer; image = cfg.imageServer;
volumes = [ "${cfg.configPath}:/root/.your-spotify" ]; volumes = [ "${cfg.configDir}:/root/.your-spotify" ];
ports = [ "${cfg.portServer}:8080" ]; ports = [ "${toString cfg.serverPort}:8080" ];
dependsOn = [ "mongo" ]; dependsOn = [ "mongo" ];
environment = { environment = {
PUID = cfg.puid; PUID = cfg.puid;
@@ -25,16 +33,17 @@ in
TZ = cfg.timeZone; TZ = cfg.timeZone;
API_ENDPOINT = "https://your-spotify-server.mjallen.dev"; API_ENDPOINT = "https://your-spotify-server.mjallen.dev";
CLIENT_ENDPOINT = "https://your-spotify.mjallen.dev"; CLIENT_ENDPOINT = "https://your-spotify.mjallen.dev";
# TODO: move Spotify API keys to sops secrets
SPOTIFY_PUBLIC = "e270589d72a6494680a17d325af8670d"; SPOTIFY_PUBLIC = "e270589d72a6494680a17d325af8670d";
SPOTIFY_SECRET = "423cb7b69fe8486e89eccd01e0c22924"; SPOTIFY_SECRET = "423cb7b69fe8486e89eccd01e0c22924";
MONGO_ENDPOINT = "mongodb://10.0.1.3:27017"; MONGO_ENDPOINT = "mongodb://10.0.1.3:27017";
}; };
}; };
virtualisation.oci-containers.containers."${cfg.name}-web" = { virtualisation.oci-containers.containers."${name}-web" = {
autoStart = true; autoStart = true;
image = cfg.imageWeb; image = cfg.imageWeb;
ports = [ "${cfg.portWeb}:3000" ]; ports = [ "${toString cfg.webPort}:3000" ];
environment = { environment = {
PUID = cfg.puid; PUID = cfg.puid;
PGID = cfg.pgid; PGID = cfg.pgid;
@@ -43,4 +52,6 @@ in
}; };
}; };
}; };
})
];
} }

View File

@@ -1,57 +0,0 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.your_spotify = {
enable = mkEnableOption "your_spotify docker service";
autoStart = mkOption {
type = types.bool;
default = true;
};
portServer = mkOption {
type = types.str;
default = "7777";
};
portWeb = mkOption {
type = types.str;
default = "7778";
};
name = mkOption {
type = types.str;
default = "your_spotify";
};
imageServer = mkOption {
type = types.str;
default = "yooooomi/your_spotify_server";
};
imageWeb = mkOption {
type = types.str;
default = "yooooomi/your_spotify_client";
};
configPath = mkOption {
type = types.str;
default = "/var/lib/your-spotify";
};
puid = mkOption {
type = types.str;
default = "911";
};
pgid = mkOption {
type = types.str;
default = "100";
};
timeZone = mkOption {
type = types.str;
default = "UTC";
};
};
}

View File

@@ -9,9 +9,10 @@ let
cfg = config.${namespace}.sops; cfg = config.${namespace}.sops;
defaultSops = lib.snowfall.fs.get-file "secrets/secrets.yaml"; defaultSops = lib.snowfall.fs.get-file "secrets/secrets.yaml";
isx86 = system == "x86_64-linux"; isx86 = system == "x86_64-linux";
user = config.${namespace}.user.name;
in in
{ {
imports = [ ./options.nix ];
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
sops = { sops = {
defaultSopsFile = if cfg.defaultSopsFile != null then cfg.defaultSopsFile else defaultSops; defaultSopsFile = if cfg.defaultSopsFile != null then cfg.defaultSopsFile else defaultSops;
@@ -19,16 +20,13 @@ in
secrets = { secrets = {
"wifi" = { }; "wifi" = { };
"disk-key".mode = "0600";
"matt_password" = { "matt_password" = {
neededForUsers = true; neededForUsers = true;
mode = "0600"; mode = "0600";
owner = config.users.users."${user}".name;
group = config.users.users."${user}".group;
}; };
"disk-key".mode = "0600";
"secureboot/GUID" = lib.mkIf isx86 { mode = "0600"; }; "secureboot/GUID" = lib.mkIf isx86 { mode = "0600"; };
"secureboot/keys/db-key" = lib.mkIf isx86 { mode = "0600"; }; "secureboot/keys/db-key" = lib.mkIf isx86 { mode = "0600"; };
"secureboot/keys/db-pem" = lib.mkIf isx86 { mode = "0600"; }; "secureboot/keys/db-pem" = lib.mkIf isx86 { mode = "0600"; };
@@ -37,8 +35,6 @@ in
"secureboot/keys/PK-key" = lib.mkIf isx86 { mode = "0600"; }; "secureboot/keys/PK-key" = lib.mkIf isx86 { mode = "0600"; };
"secureboot/keys/PK-pem" = lib.mkIf isx86 { mode = "0600"; }; "secureboot/keys/PK-pem" = lib.mkIf isx86 { mode = "0600"; };
}; };
templates = { };
}; };
}; };
} }

View File

@@ -5,7 +5,7 @@ with lib;
enable = mkEnableOption "enable sops"; enable = mkEnableOption "enable sops";
defaultSopsFile = mkOption { defaultSopsFile = mkOption {
type = types.nullOr types.str; type = types.nullOr types.path;
default = null; default = null;
description = "Default sops file to use for secrets. If null, will use the system-wide default."; description = "Default sops file to use for secrets. If null, will use the system-wide default.";
example = "/etc/nixos/secrets/secrets.yaml"; example = "/etc/nixos/secrets/secrets.yaml";

View File

@@ -8,20 +8,27 @@
with lib; with lib;
let let
inherit (lib.${namespace}) mkOpt mkBoolOpt; inherit (lib.${namespace}) mkOpt mkBoolOpt;
cfg = config.${namespace}.user // { cfg = config.${namespace}.user;
hashedPasswordFile = (
if # Reference the sops-managed password file only when the secret has been
( # declared somewhere in the configuration. Checking the attrset with ?
config.${namespace}.user.hashedPassword == null # avoids forcing evaluation of the secret path on hosts that don't use sops.
&& config.${namespace}.user.hashedPasswordFile == null sopsMattPassword =
&& config.${namespace}.user.password == null let
) secretName = cfg.sopsPasswordSecret;
then in
defaultPasswordFile if secretName != null && builtins.hasAttr secretName config.sops.secrets then
config.sops.secrets.${secretName}.path
else else
config.${namespace}.user.hashedPasswordFile null;
);
}; # Fall back to the sops-managed password file only when no explicit password
# method has been set by the caller.
resolvedPasswordFile =
if cfg.hashedPassword == null && cfg.hashedPasswordFile == null && cfg.password == null then
sopsMattPassword
else
cfg.hashedPasswordFile;
# Common SSH keys used across systems # Common SSH keys used across systems
commonSshKeys = [ commonSshKeys = [
@@ -39,7 +46,6 @@ let
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGdwsYDOkjd17rKdpjKN+3Yx1rRHT/Fiv2erc2JdE6ibHKBxLSEZ4kCOFCyGyc5ZO6Cmb09GfAe9FugkD4titns= cardno:33_720_987" "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGdwsYDOkjd17rKdpjKN+3Yx1rRHT/Fiv2erc2JdE6ibHKBxLSEZ4kCOFCyGyc5ZO6Cmb09GfAe9FugkD4titns= cardno:33_720_987"
]; ];
defaultPasswordFile = config.sops.secrets."matt_password".path;
in in
{ {
options.${namespace}.user = with types; { options.${namespace}.user = with types; {
@@ -73,7 +79,11 @@ in
hashedPasswordFile = mkOpt (nullOr path) null "Path to the password file for this user account"; hashedPasswordFile = mkOpt (nullOr path) null "Path to the password file for this user account";
mutableUsers = mkBoolOpt false "Whether users are mutable (can be modified after creation)."; sopsPasswordSecret =
mkOpt (nullOr str) "matt_password"
"Name of the sops secret to use as the hashed password file when no explicit password method is set. Set to null to disable the sops fallback.";
mutableUsers = mkBoolOpt false "Whether users are mutable (can be modified after modification).";
}; };
config = { config = {
@@ -94,8 +104,8 @@ in
packages packages
password password
hashedPassword hashedPassword
hashedPasswordFile
; ;
hashedPasswordFile = resolvedPasswordFile;
extraGroups = [ extraGroups = [
"wheel" "wheel"
@@ -137,8 +147,8 @@ in
assertions = [ assertions = [
{ {
assertion = assertion =
(cfg.password != null) || (cfg.hashedPassword != null) || (cfg.hashedPasswordFile != null); (cfg.password != null) || (cfg.hashedPassword != null) || (resolvedPasswordFile != null);
message = "User '${cfg.name}' requires at least one password method (password, hashedPassword, or hashedPasswordFile)."; message = "User '${cfg.name}' requires at least one password method (password, hashedPassword, hashedPasswordFile, or a sops 'matt_password' secret).";
} }
{ {
assertion = assertion =
@@ -146,19 +156,11 @@ in
passwordMethods = lib.count (x: x != null) [ passwordMethods = lib.count (x: x != null) [
cfg.password cfg.password
cfg.hashedPassword cfg.hashedPassword
cfg.hashedPasswordFile resolvedPasswordFile
]; ];
in in
passwordMethods <= 1; passwordMethods <= 1;
message = "User '${cfg.name}' can only use one password method at a time. Found multiple: ${ message = "User '${cfg.name}' can only use one password method at a time.";
lib.concatStringsSep ", " (
lib.filter (x: x != null) [
(if cfg.password != null then "password" else null)
(if cfg.hashedPassword != null then "hashedPassword" else null)
(if cfg.hashedPasswordFile != null then "hashedPasswordFile" else null)
]
)
}";
} }
]; ];
}; };

View File

@@ -23,6 +23,8 @@
${namespace} = { ${namespace} = {
headless.enable = false; headless.enable = false;
sops.enable = true;
impermanence = { impermanence = {
enable = true; enable = true;
# extraDirectories = [ # extraDirectories = [

View File

@@ -17,6 +17,8 @@
${namespace} = { ${namespace} = {
sops.enable = true;
# ################################################### # ###################################################
# # Impermanence # # # # Impermanence # #
# ################################################### # ###################################################

View File

@@ -27,6 +27,8 @@
${namespace} = { ${namespace} = {
headless.enable = false; headless.enable = false;
sops.enable = true;
bootloader.lanzaboote.enable = true; bootloader.lanzaboote.enable = true;
desktop.gnome.enable = true; desktop.gnome.enable = true;

View File

@@ -19,10 +19,7 @@ in
ai = { ai = {
enable = true; enable = true;
}; };
arrs = { arrs.enable = true;
enable = true;
enableVpn = true;
};
attic = { attic = {
enable = true; enable = true;
port = 9012; port = 9012;

View File

@@ -26,6 +26,7 @@ in
powerManagement.cpuFreqGovernor = "powersave"; powerManagement.cpuFreqGovernor = "powersave";
${namespace} = { ${namespace} = {
sops.enable = true;
# ################################################### # ###################################################
# # Boot # # # # Boot # #
# ################################################### # ###################################################

View File

@@ -17,6 +17,7 @@ in
consoleLogLevel = 3; consoleLogLevel = 3;
}; };
${namespace} = { ${namespace} = {
sops.enable = true;
services = { services = {
actual = mkForce disabled; actual = mkForce disabled;
ai = mkForce disabled; ai = mkForce disabled;

View File

@@ -21,15 +21,17 @@ let
}; };
in in
{ {
# Bespoke services that define their own path options (not via mkModule). imports =
# Set NAS-specific paths here so the module defaults stay generic. # Bespoke services with their own path option names (not configDir/dataDir).
${namespace}.services.your_spotify.configPath = lib.mkDefault "${appdata}/your_spotify"; [
{
${namespace}.services.ocis = { ${namespace}.services.ocis = {
dataPath = lib.mkDefault "${data}/ocis"; dataPath = lib.mkDefault "${data}/ocis";
configPath = lib.mkDefault "${appdata}/ocis"; configPath = lib.mkDefault "${appdata}/ocis";
}; };
}
imports = map svcDefault [ ]
++ map svcDefault [
"actual" "actual"
"ai" "ai"
"arrs" "arrs"
@@ -82,5 +84,6 @@ in
"unmanic" "unmanic"
"uptime-kuma" "uptime-kuma"
"wyoming" "wyoming"
"your-spotify"
]; ];
} }

View File

@@ -31,6 +31,8 @@
${namespace} = { ${namespace} = {
headless.enable = false; headless.enable = false;
sops.enable = true;
bootloader.lanzaboote.enable = true; bootloader.lanzaboote.enable = true;
desktop = { desktop = {
@@ -100,6 +102,7 @@
"cosmic" = { "cosmic" = {
configuration = { configuration = {
${namespace} = { ${namespace} = {
sops.enable = true;
desktop = { desktop = {
cosmic.enable = lib.mkForce true; cosmic.enable = lib.mkForce true;
hyprland = { hyprland = {

View File

@@ -28,6 +28,13 @@ in
# Secrets # Secrets
# ------------------------------ # ------------------------------
secrets = { secrets = {
"matt_password" = {
neededForUsers = true;
mode = "0600";
owner = config.users.users."${user}".name;
group = config.users.users."${user}".group;
};
"desktop/hass_token" = { "desktop/hass_token" = {
sopsFile = desktopSopsFile; sopsFile = desktopSopsFile;
mode = "0777"; mode = "0777";

View File

@@ -5,6 +5,7 @@
}: }:
{ {
${namespace} = { ${namespace} = {
sops.enable = true;
# ################################################### # ###################################################
# # Boot # # # # Boot # #
# ################################################### # ###################################################