diff --git a/deployments.nix b/deployments.nix index f99a14b..6469c14 100644 --- a/deployments.nix +++ b/deployments.nix @@ -128,4 +128,9 @@ module.authentik.enable = true; namespace = "authentik"; }; + + mealie = { + module.mealie.enable = true; + namespace = "mealie"; + }; } diff --git a/modules/bootstrap-default.nix b/modules/bootstrap-default.nix index 31e18f8..25bc712 100644 --- a/modules/bootstrap-default.nix +++ b/modules/bootstrap-default.nix @@ -63,6 +63,7 @@ tailscale = {}; ntfy = {}; authentik = {}; + mealie = {}; }; nodes = @@ -134,6 +135,7 @@ keepassxc.storage = "100Mi"; authentik-db.storage = "10Gi"; authentik-redis.storage = "5Gi"; + mealie.storage = "3Gi"; }; tailscaleIngresses.tailscale-longhorn = { diff --git a/modules/default.nix b/modules/default.nix index ef212e2..f730e1c 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -30,5 +30,6 @@ ./ntfy.nix ./minecraft.nix ./authentik.nix + ./mealie.nix ]; } diff --git a/modules/mealie.nix b/modules/mealie.nix new file mode 100644 index 0000000..df3db99 --- /dev/null +++ b/modules/mealie.nix @@ -0,0 +1,76 @@ +{ + lib, + config, + utils, + ... +}: { + options.mealie.enable = lib.mkEnableOption "mealie"; + + config = lib.mkIf config.mealie.enable { + kubernetes.resources = { + deployments.mealie.spec = { + selector.matchLabels.app = "mealie"; + + strategy = { + type = "RollingUpdate"; + + rollingUpdate = { + maxSurge = 0; + maxUnavailable = 1; + }; + }; + + template = { + metadata.labels.app = "mealie"; + + spec = { + containers.mealie = { + image = utils.mkNixNGImage "mealie"; + ports.web.containerPort = 8000; + + env = { + SMTP_USER.value = "ref+sops://secrets.yml#/smtp2go/username"; + SMTP_PASSWORD.value = "ref+sops://secrets.yml#/smtp2go/password"; + OIDC_CLIENT_SECRET.value = "ref+sops://secrets.yml#/authentik/oauth2/mealie/client_secret"; + }; + + volumeMounts = [ + { + name = "mealie"; + mountPath = "/data"; + } + ]; + }; + + volumes.mealie.persistentVolumeClaim.claimName = "mealie"; + }; + }; + }; + + services.mealie.spec = { + selector.app = "mealie"; + + ports.web = { + port = 80; + targetPort = "web"; + }; + }; + }; + + lab = { + ingresses.mealie = { + host = "mealie.kun.is"; + + service = { + name = "mealie"; + portName = "web"; + }; + }; + + longhorn.persistentVolumeClaim.mealie = { + volumeName = "mealie"; + storage = "3Gi"; + }; + }; + }; +} diff --git a/nixng-configurations/default.nix b/nixng-configurations/default.nix index 5a9c943..c1803c8 100644 --- a/nixng-configurations/default.nix +++ b/nixng-configurations/default.nix @@ -21,6 +21,7 @@ flake-utils.lib.eachDefaultSystem (system: let prowlarr = ./prowlarr.nix; blog = ./blog.nix; deluge = ./deluge.nix; + mealie = ./mealie.nix; }; in { nixngConfigurations = builtins.mapAttrs (name: configFile: @@ -43,6 +44,7 @@ in { self.nixngModules.sonarr self.nixngModules.prowlarr self.nixngModules.deluge + self.nixngModules.mealie { nixpkgs.overlays = [ (_final: _prev: { diff --git a/nixng-configurations/mealie.nix b/nixng-configurations/mealie.nix new file mode 100644 index 0000000..bd3350c --- /dev/null +++ b/nixng-configurations/mealie.nix @@ -0,0 +1,25 @@ +{ + dinit.enable = true; + init.services.mealie.shutdownOnExit = true; + + services.mealie = { + enable = true; + + settings = { + DATA_DIR = "/data"; + BASE_URL = "https://mealie.kun.is"; + ALLOW_SIGNUP = "False"; + SMTP_HOST = "mail.smtp2go.com"; + SMTP_PORT = "2525"; + SMTP_FROM_NAME = "Mealie"; + SMTP_AUTH_STRATEGY = "ssl"; + SMTP_FROM_EMAIL = "mealie@kun.is"; + OIDC_AUTH_ENABLED = "True"; + OIDC_CONFIGURATION_URL = "https://authentik.kun.is/application/o/mealie/.well-known/openid-configuration"; + OIDC_CLIENT_ID = "lvkHoIPacUXjY4jr9YyEQC7YyhccOH0atbpOiKmG"; + OIDC_AUTO_REDIRECT = "True"; + OIDC_PROVIDER_NAME = "Authentik"; + OIDC_REMEMBER_ME = "True"; + }; + }; +} diff --git a/nixng-modules/default.nix b/nixng-modules/default.nix index 63903ab..629a244 100644 --- a/nixng-modules/default.nix +++ b/nixng-modules/default.nix @@ -1,4 +1,4 @@ -{...}: { +_: { nixngModules = { bazarr = import ./bazarr.nix; radicale = import ./radicale.nix; @@ -8,5 +8,6 @@ prowlarr = import ./prowlarr.nix; ids = import ./ids.nix; deluge = import ./deluge.nix; + mealie = import ./mealie.nix; }; } diff --git a/nixng-modules/ids.nix b/nixng-modules/ids.nix index fce4278..228b5da 100644 --- a/nixng-modules/ids.nix +++ b/nixng-modules/ids.nix @@ -1,4 +1,4 @@ -{...}: { +{ ids = { uids = { radicale = 408; @@ -8,6 +8,7 @@ bazarr = 412; prowlarr = 413; deluge = 414; + mealie = 415; }; gids = { @@ -19,6 +20,7 @@ bazarr = 412; prowlarr = 413; deluge = 414; + mealie = 415; }; }; } diff --git a/nixng-modules/mealie.nix b/nixng-modules/mealie.nix new file mode 100644 index 0000000..583d4b4 --- /dev/null +++ b/nixng-modules/mealie.nix @@ -0,0 +1,85 @@ +{ + lib, + nglib, + pkgs, + config, + ... +}: let + cfg = config.services.mealie; + cfgInit = config.init.services.mealie; +in { + options.services.mealie = { + enable = lib.mkEnableOption "mealie"; + package = lib.mkPackageOption pkgs "mealie" {}; + settings = lib.mkOption { + type = lib.types.submodule { + freeformType = with lib.types; attrsOf str; + + options = { + PRODUCTION = lib.mkOption { + type = lib.types.str; + default = "true"; + }; + + DATA_DIR = lib.mkOption { + type = with lib.types; nullOr str; + default = null; + }; + + DB_ENGINE = lib.mkOption { + type = with lib.types; nullOr str; + default = "sqlite"; + }; + + ALEMBIC_CONFIG_FILE = lib.mkOption { + type = with lib.types; nullOr str; + default = "${cfg.package}/alembic.ini"; + }; + }; + }; + + description = '' + Configuration of the Mealie service. + + See [the Mealie documentation](https://nightly.mealie.io/documentation/getting-started/installation/backend-config/) for available options and default values. + ''; + default = {}; + }; + }; + + config = lib.mkIf cfg.enable { + init.services.mealie = { + enabled = true; + user = lib.mkDefault "mealie"; + group = lib.mkDefault "mealie"; + + tmpfiles = with nglib.nottmpfiles.dsl; lib.optional (cfg.settings.DATA_DIR != null) (d "${cfg.settings.DATA_DIR}" "-" cfgInit.user cfgInit.group "-" _); + + execStart = + pkgs.writeShellScript "mealie-run" + (let + # Mealie can only be configured via environmental variables. + # With this, we don't accidentally overwrite env variables set by the user. + extraEnvLines = lib.mapAttrsToList (key: value: ''export ${key}=''${${key}:=${value}}'') cfg.settings; + in '' + ${lib.concatStringsSep "\n" extraEnvLines} + ${cfg.package}/libexec/init_db + + ${lib.getExe cfg.package} -b 0.0.0.0:8000 + ''); + }; + + environment.systemPackages = [cfg.package]; + + users.users.${cfgInit.user} = nglib.mkDefaultRec { + description = "mealie"; + inherit (cfgInit) group; + createHome = false; + home = "/var/empty"; + useDefaultShell = true; + uid = config.ids.uids.mealie; + }; + + users.groups.${cfgInit.group} = nglib.mkDefaultRec {gid = config.ids.gids.mealie;}; + }; +} diff --git a/nixng-modules/radarr.nix b/nixng-modules/radarr.nix index 965f387..8ea2b39 100644 --- a/nixng-modules/radarr.nix +++ b/nixng-modules/radarr.nix @@ -35,7 +35,7 @@ in { users.users.${cfgInit.user} = lib.mkIf (cfgInit.user == "radarr") (nglib.mkDefaultRec { description = "radarr"; - group = cfgInit.group; + inherit (cfgInit) group; createHome = false; home = "/var/empty"; useDefaultShell = true; diff --git a/secrets.yml b/secrets.yml index 33ad123..8d8e5c1 100644 --- a/secrets.yml +++ b/secrets.yml @@ -52,6 +52,8 @@ authentik: client_secret: ENC[AES256_GCM,data:zLejYbfudK/4OquLXPYTv9YOmFpCVfg0KLNkDSDCpFrxroDUAXBCLtYXiGuYkYrD/t7LAzRt+OTq70d7ciuHhBNSLclP2U97BQoXCWscWnxQauRZ+UCABvP+DB9VPQmCwU+uKPrKQ8l51baj+MkpIDdk2lwavpONMU57Zov6N2o=,iv:aQ4bsXUXn177tCxe1kAsSMP9ynEzvDwN0hwFhrT3Nko=,tag:EFcnf6VmyFt2i4+aL56sWw==,type:str] kitchenowl: client_secret: ENC[AES256_GCM,data:x4Xsd3d3El59HKBYNV56ah314hYSRhzt46upW34cOopXNHSB3zCDrD46LUa6i8g6V5GJyrMpMfO5mv+b80JrmfHkhGUXZXuTwDNu6ijnO6ZCvC2Bdlo+T0tlkJe25OMCBseJkkC++UBrpKQQTAhyVjnPSVrGVvtY4WtdAw+X/OY=,iv:pOowIhPD7kb2F3ylFzLwNW3BhPZyzoFCGRm2+KCmhno=,tag:GxFI0w06EyGxFwj6Fv4ZLQ==,type:str] + mealie: + client_secret: ENC[AES256_GCM,data:VNEV8a1KZc6XVeRzyBWzuwldTmxEepPRUOEMEM3HKrDIkxcGHDuoLh5P7Ti+jS5rbmua+ET4GPcJTYXR+pO5/cMaxqFONj1D1w9541QPYZNBbTfPM/Zfu8OnzngVsCnnKEtu1bVwflUnmf7F5hHED8zJRe1F9PT/HYA6NCd4ajQ=,iv:58ysTItP8UNnQWwgWRS1dk/K/2dJv3P5wa5rGnz2P/I=,tag:vLGrFldzOey9ANW010GylA==,type:str] smtp2go: username: ENC[AES256_GCM,data:BEr7Rq7rlGvfYEpY/ZXnhM2eClnHdqU81A==,iv:dwYD5h+C5bzS9ikUgxQ51+jRQ32TtDy2PhDbd1tpS8Q=,tag:CjjLDz5n4H28qi8jWf9S4w==,type:str] password: ENC[AES256_GCM,data:Yys6qy6DRYo16+X+Uj9oa9otjaKBnHOtIQ==,iv:G7H9mxsODShFoVlNMwuV8O18NBG/7LTFDFdqnH83YkE=,tag:hSlYp27QMoPZwiKBqyOpKA==,type:str] @@ -79,8 +81,8 @@ sops: azR0UkJyL0RwUVk4ZzdkSWptcDlWVjAK5FU9B5TBSnV3azO4eCv13T6i3dGGuI68 UgBrVEb1/Fv+4XTjeSEhpiOaH8sNWYoNa3Aa7uTZYlHDRWga2GC7zw== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-02-13T16:43:24Z" - mac: ENC[AES256_GCM,data:EJ3TwNwTEsbA2Y/v7ZNgRq3ENgl1tyIzTbrW3x58p5MA6sPMCshVnu6cqrssn3l/cHZdGYxeyachVbqbaVC60Gbw1UiywkjAj5w5l92PMne142unjeLDsVgGv3ItalWLgmWBVp6B1YfxID9V5CxNZjSglVzH3o0bseqIGnvcDrQ=,iv:dK2QR6s5m9BCW+7ZXwE0Ksca0EAGtHtrTfigbUkY2AY=,tag:+HUoCt7tu5yDCG3LbwEq8w==,type:str] + lastmodified: "2025-02-15T15:37:53Z" + mac: ENC[AES256_GCM,data:tsoDYbuhxEH3PrxOPgfKczD8Hh1XGJRhGAtm2DWpPP9T99ub/l3KAV2pInvUi5Kn+1QvhJUAwFAP6A/435cqfsHxQI066N7ADUYO4qshcsAYKK7ofBVNnI431D3oD+kBujWKmvSqhlamdP+O7O1ICtbfI5PEM8SN5KWEvEtyp9A=,iv:pDiPy6EWLaZQbNydRFTktRlcf7M9Uf8OS+WPbQkUx9M=,tag:D+tMTFVbWE7TQIw/0MUZjw==,type:str] pgp: [] unencrypted_suffix: _unencrypted version: 3.9.4