From 8d47f6b09cd9e730b829b49461251147e1b39994 Mon Sep 17 00:00:00 2001 From: fleaz Date: Wed, 10 Jan 2024 00:33:04 +0100 Subject: [PATCH] added mealie --- brother-ql.nix => brother-ql/default.nix | 0 default.nix | 14 ++-- mealie/default.nix | 97 ++++++++++++++++++++++ mealie/mealie-frontend.nix | 46 ++++++++++ mealie/mealie_init_db.patch | 25 ++++++ mealie/mealie_logger.patch | 18 ++++ mealie/mealie_statedir.patch | 25 ++++++ modules/default.nix | 1 + modules/mealie.nix | 77 +++++++++++++++++ neolink.nix => neolink/default.nix | 0 studio-link.nix => studio-link/default.nix | 0 11 files changed, 295 insertions(+), 8 deletions(-) rename brother-ql.nix => brother-ql/default.nix (100%) create mode 100644 mealie/default.nix create mode 100644 mealie/mealie-frontend.nix create mode 100644 mealie/mealie_init_db.patch create mode 100644 mealie/mealie_logger.patch create mode 100644 mealie/mealie_statedir.patch create mode 100644 modules/mealie.nix rename neolink.nix => neolink/default.nix (100%) rename studio-link.nix => studio-link/default.nix (100%) diff --git a/brother-ql.nix b/brother-ql/default.nix similarity index 100% rename from brother-ql.nix rename to brother-ql/default.nix diff --git a/default.nix b/default.nix index d972755..ea2ed8f 100644 --- a/default.nix +++ b/default.nix @@ -4,18 +4,16 @@ self: super: { python3 = super.python3.override { packageOverrides = python-self: python-super: { - pytapo = python-self.callPackage ./pytapo { }; - brother-ql = python-self.callPackage ./brother-ql.nix { }; + pytapo = python-self.callPackage ./pytapo { }; + brother-ql = python-self.callPackage ./brother-ql { }; }; }; #gotosocial = super.callPackage ./gotosocial { }; pulse-secure = super.callPackage ./pulse-secure { }; - studio-link = super.callPackage ./studio-link.nix { }; - + studio-link = super.callPackage ./studio-link { }; nginxModules = super.recurseIntoAttrs (super.callPackage ./nginx/modules.nix { }); - - libedgetpu = self.callPackage ./libedgetpu {}; - - neolink = self.callPackage ./neolink.nix {}; + libedgetpu = self.callPackage ./libedgetpu { }; + neolink = self.callPackage ./neolink { }; + mealie = self.callPackage ./mealie { }; } diff --git a/mealie/default.nix b/mealie/default.nix new file mode 100644 index 0000000..64edd16 --- /dev/null +++ b/mealie/default.nix @@ -0,0 +1,97 @@ +{ lib, callPackage, fetchFromGitHub, gnused, python3, python3Packages, writeShellScript }: let + version = "1.0.0-RC2"; + src = fetchFromGitHub { + owner = "mealie-recipes"; + repo = "mealie"; + rev = "v${version}"; + sha256 = "sha256-/sht8s0Nap6TdYxAqamKj/HGGV21/8eYCuYzpWXRJCE="; + }; + + frontend = callPackage (import ./mealie-frontend.nix src version) { }; + +in python3Packages.buildPythonPackage rec { + pname = "mealie"; + inherit version src; + format = "pyproject"; + + patches = [ + ./mealie_statedir.patch + ./mealie_init_db.patch + ./mealie_logger.patch + ]; + + nativeBuildInputs = [ + python3Packages.poetry-core + python3.pkgs.pythonRelaxDepsHook + ]; + + pythonRelaxDeps = true; + + propagatedBuildInputs = with python3Packages; [ + aiofiles + alembic + aniso8601 + appdirs + apprise + bcrypt + extruct + fastapi + gunicorn + jinja2 + lxml + orjson + passlib + pillow + psycopg2 + pyhumps + pytesseract + python-dotenv + python-jose + python-ldap + python-multipart + python-slugify + pyyaml + rapidfuzz + recipe-scrapers + sqlalchemy + uvicorn + ]; + + doCheck = true; + + postInstall = let + start_script = writeShellScript "start-mealie" '' + export STATIC_FILES="${frontend}" + ${python3Packages.gunicorn}/bin/gunicorn "$@" -k uvicorn.workers.UvicornWorker mealie.app:app; + ''; + in '' + mkdir -p $out/config $out/bin + ${lib.getExe gnused} 's+script_location = alembic+script_location = ${src}/alembic+g' ${src}/alembic.ini > $out/config/alembic.ini + + rm -f $out/bin/* + ln -s $out/lib/${python3.libPrefix}/site-packages/mealie/db/init_db.py $out/bin/init_db.py + cp ${start_script} $out/bin/start-mealie + chmod +x $out/bin/start-mealie + ''; + + checkInputs = with python3Packages; [ + pytestCheckHook + ]; + + passthru = { + inherit python3; + }; + + meta = with lib; { + description = "A Place for All Your Recipes"; + longDescription = '' + Mealie is a self hosted recipe manager and meal planner with a RestAPI backend and a reactive frontend + application built in NuxtJS for a pleasant user experience for the whole family. Easily add recipes into your + database by providing the URL and Mealie will automatically import the relevant data or add a family recipe with + the UI editor. + ''; + homepage = "https://nightly.mealie.io"; + license = licenses.agpl3Only; + maintainers = with maintainers; [ litchipi ]; + }; +} diff --git a/mealie/mealie-frontend.nix b/mealie/mealie-frontend.nix new file mode 100644 index 0000000..d133c9a --- /dev/null +++ b/mealie/mealie-frontend.nix @@ -0,0 +1,46 @@ +src: version: +{ lib, fetchYarnDeps, nodejs_18, prefetch-yarn-deps, stdenv, ... }: stdenv.mkDerivation { + name = "mealie-frontend"; + inherit version; + src = "${src}/frontend"; + + yarnOfflineCache = fetchYarnDeps { + yarnLock = "${src}/frontend/yarn.lock"; + hash = "sha256-zQUD/PQWzp2Q6fiVmLicvSusXffu6s9q3x/aAUnCN38="; + }; + + nativeBuildInputs = [ + prefetch-yarn-deps + nodejs_18 + nodejs_18.pkgs.yarn + ]; + + configurePhase = '' + runHook preConfigure + + export HOME=$(mktemp -d) + yarn config --offline set yarn-offline-mirror "$yarnOfflineCache" + fixup-yarn-lock yarn.lock + command -v yarn + yarn install --frozen-lockfile --offline --no-progress --non-interactive + patchShebangs node_modules/ + + runHook postConfigure + ''; + + buildPhase = '' + runHook preBuild + + export NUXT_TELEMETRY_DISABLED=1 + yarn --offline build + yarn --offline generate + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + mv dist $out + runHook postInstall + ''; +} diff --git a/mealie/mealie_init_db.patch b/mealie/mealie_init_db.patch new file mode 100644 index 0000000..e4d8c52 --- /dev/null +++ b/mealie/mealie_init_db.patch @@ -0,0 +1,25 @@ +diff --git a/mealie/db/init_db.py b/mealie/db/init_db.py +index 90dd91a8..d19a5eac 100644 +--- a/mealie/db/init_db.py ++++ b/mealie/db/init_db.py +@@ -1,3 +1,4 @@ ++import os + from collections.abc import Callable + from pathlib import Path + from time import sleep +@@ -85,7 +86,14 @@ def main(): + if max_retry == 0: + raise ConnectionError("Database connection failed - exiting application.") + +- alembic_cfg = Config(str(PROJECT_DIR / "alembic.ini")) ++ alembic_cfg_path = os.getenv("ALEMBIC_CONFIG_FPATH") ++ if not alembic_cfg_path: ++ alembic_cfg_path = str(PROJECT_DIR / "alembic.ini") ++ ++ if not os.path.isfile(alembic_cfg_path): ++ raise Exception("Provided alembic config path doesn't exist") ++ ++ alembic_cfg = Config(alembic_cfg_path) + if db_is_at_head(alembic_cfg): + logger.debug("Migration not needed.") + else: diff --git a/mealie/mealie_logger.patch b/mealie/mealie_logger.patch new file mode 100644 index 0000000..b601520 --- /dev/null +++ b/mealie/mealie_logger.patch @@ -0,0 +1,18 @@ +diff --git a/mealie/core/root_logger.py b/mealie/core/root_logger.py +index 29db504f..30fc05dc 100644 +--- a/mealie/core/root_logger.py ++++ b/mealie/core/root_logger.py +@@ -9,7 +9,12 @@ DATA_DIR = determine_data_dir() + + from .config import get_app_settings # noqa E402 + +-LOGGER_FILE = DATA_DIR.joinpath("mealie.log") ++import os ++ ++LOGGER_FILE = os.getenv("MEALIE_LOG_FILE") ++if not LOGGER_FILE: ++ LOGGER_FILE = DATA_DIR.joinpath("mealie.log") ++ + DATE_FORMAT = "%d-%b-%y %H:%M:%S" + LOGGER_FORMAT = "%(levelname)s: %(asctime)s \t%(message)s" + diff --git a/mealie/mealie_statedir.patch b/mealie/mealie_statedir.patch new file mode 100644 index 0000000..f310c15 --- /dev/null +++ b/mealie/mealie_statedir.patch @@ -0,0 +1,25 @@ +diff --git a/mealie/core/config.py b/mealie/core/config.py +index 7dae9b23..71769098 100644 +--- a/mealie/core/config.py ++++ b/mealie/core/config.py +@@ -19,15 +19,15 @@ DATA_DIR = os.getenv("DATA_DIR") + + + def determine_data_dir() -> Path: +- global PRODUCTION, TESTING, BASE_DIR, DATA_DIR ++ global TESTING + + if TESTING: + return BASE_DIR.joinpath(DATA_DIR if DATA_DIR else "tests/.temp") + +- if PRODUCTION: +- return Path(DATA_DIR if DATA_DIR else "/app/data") +- +- return BASE_DIR.joinpath("dev", "data") ++ state_dir = os.getenv("STATE_DIRECTORY") ++ if not state_dir: ++ raise Exception("State directory not set") ++ return Path(state_dir) + + + @lru_cache diff --git a/modules/default.nix b/modules/default.nix index 3e8f981..017b18b 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -1,5 +1,6 @@ { imports = [ #./gotosocial.nix + ./mealie.nix ]; } diff --git a/modules/mealie.nix b/modules/mealie.nix new file mode 100644 index 0000000..db8d734 --- /dev/null +++ b/modules/mealie.nix @@ -0,0 +1,77 @@ +{ config, lib, pkgs, ...}: +let + cfg = config.services.mealie; + pkg = pkgs.mealie; +in +{ + options.services.mealie = { + enable = lib.mkEnableOption "Mealie, a recipe manager and meal planner"; + + listen_address = lib.mkOption { + type = lib.types.str; + default = "0.0.0.0"; + description = "Address on which the service should listen"; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 9000; + description = "Port on which to serve the Mealie service"; + }; + + extraConfig = lib.mkOption { + type = lib.types.attrs; + default = {}; + description = lib.mdDoc '' + Some extra configuration for the service. + See [the mealie documentation](https://nightly.mealie.io/documentation/getting-started/installation/backend-config/) for available options and default values. + ''; + example = { + ALLOW_SIGNUP = "false"; + }; + }; + + credentialsFile = lib.mkOption { + type = with lib.types; nullOr path; + default = null; + example = "/run/secrets/mealie-credentials.env"; + description = '' + File containing credentials used in mealie such as POSTGRES_PASSWORD + or sensitive LDAP options. + + Expects the format of an EnvironmentFile=, as described by systemd.exec(5). + ''; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.mealie = { + description = "Mealie, a self hosted recipe manager and meal planner"; + after = [ + "network.target" + "network-online.target" + ]; + wantedBy = [ + "multi-user.target" + ]; + + environment = { + PYTHONPATH = "${pkg.python3.pkgs.makePythonPath pkg.propagatedBuildInputs}:${pkg}/lib/${pkg.python3.libPrefix}/site-packages"; + PRODUCTION = "true"; + MEALIE_LOG_FILE = "/var/log/mealie/mealie.log"; + ALEMBIC_CONFIG_FPATH="${pkg}/config/alembic.ini"; + API_PORT = builtins.toString cfg.port; + } // (builtins.mapAttrs (_: val: builtins.toString val) cfg.extraConfig); + + serviceConfig = { + DynamicUser = true; + User = "mealie"; + ExecStartPre = "${pkg.python3.interpreter} ${pkg}/bin/init_db.py"; + ExecStart = "${pkg}/bin/start-mealie -b ${cfg.listen_address}:${builtins.toString cfg.port}"; + EnvironmentFile = lib.optional (cfg.credentialsFile != null) cfg.credentialsFile; + StateDirectory = "mealie"; + LogsDirectory = "mealie"; + }; + }; + }; +} diff --git a/neolink.nix b/neolink/default.nix similarity index 100% rename from neolink.nix rename to neolink/default.nix diff --git a/studio-link.nix b/studio-link/default.nix similarity index 100% rename from studio-link.nix rename to studio-link/default.nix