From 11330ebea0b5ee314e7eade61fa44e48c597a3be Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 16 Mar 2026 21:59:10 -0700 Subject: [PATCH] Deploy Mealie recipe manager (#299) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Deploy Mealie (self-hosted recipe manager) on minikube-indri via ArgoCD - Build container from source via forge mirror (`mirrors/mealie`) — multi-stage Dockerfile with Node.js frontend + Python/uv backend - Add Caddy proxy entry for `meals.ops.eblu.me` - Part of a larger meal planning pipeline: Mealie stores categorized recipes, a planner script selects balanced meals, and Ollama generates unified cooking timelines ## Status - [x] Mirror mealie repo on forge - [x] Dockerfile (from-source build) - [x] ArgoCD app + k8s manifests - [x] Caddy proxy entry - [x] Service docs, routing table, app registry - [ ] Local Dagger build test - [ ] Container build + push to registry - [ ] Update kustomization.yaml with real image tag - [ ] Deploy and verify - [ ] Provision Caddy ## Test plan - Build container locally via `dagger call build --src=. --container-name=mealie` - Trigger CI build via `mise run container-build-and-release mealie` - Deploy from branch: `argocd app set mealie --revision deploy-mealie && argocd app sync mealie` - Verify Mealie UI at `https://meals.ops.eblu.me` - Verify API docs at `https://meals.ops.eblu.me/docs` Reviewed-on: https://forge.eblu.me/eblume/blumeops/pulls/299 --- ansible/roles/borgmatic/defaults/main.yml | 14 ++ ansible/roles/borgmatic/tasks/main.yml | 7 + .../roles/borgmatic/templates/config.yaml.j2 | 10 ++ ansible/roles/caddy/defaults/main.yml | 3 + argocd/apps/mealie.yaml | 17 +++ .../authentik/configmap-blueprint.yaml | 44 ++++++ .../authentik/deployment-worker.yaml | 5 + .../manifests/authentik/external-secret.yaml | 4 + argocd/manifests/mealie/deployment.yaml | 79 ++++++++++ argocd/manifests/mealie/external-secret.yaml | 19 +++ .../manifests/mealie/ingress-tailscale.yaml | 25 +++ argocd/manifests/mealie/kustomization.yaml | 15 ++ argocd/manifests/mealie/pvc.yaml | 13 ++ argocd/manifests/mealie/service.yaml | 13 ++ containers/mealie/Dockerfile | 142 ++++++++++++++++++ docs/changelog.d/deploy-mealie.feature.md | 1 + docs/explanation/federated-login.md | 3 +- docs/reference/infrastructure/routing.md | 1 + docs/reference/kubernetes/apps.md | 1 + docs/reference/services/borgmatic.md | 8 +- docs/reference/services/mealie.md | 61 ++++++++ service-versions.yaml | 7 + 22 files changed, 489 insertions(+), 3 deletions(-) create mode 100644 argocd/apps/mealie.yaml create mode 100644 argocd/manifests/mealie/deployment.yaml create mode 100644 argocd/manifests/mealie/external-secret.yaml create mode 100644 argocd/manifests/mealie/ingress-tailscale.yaml create mode 100644 argocd/manifests/mealie/kustomization.yaml create mode 100644 argocd/manifests/mealie/pvc.yaml create mode 100644 argocd/manifests/mealie/service.yaml create mode 100644 containers/mealie/Dockerfile create mode 100644 docs/changelog.d/deploy-mealie.feature.md create mode 100644 docs/reference/services/mealie.md diff --git a/ansible/roles/borgmatic/defaults/main.yml b/ansible/roles/borgmatic/defaults/main.yml index 7d2ef49..5980915 100644 --- a/ansible/roles/borgmatic/defaults/main.yml +++ b/ansible/roles/borgmatic/defaults/main.yml @@ -16,6 +16,7 @@ borgmatic_source_directories: - /opt/homebrew/var/forgejo - /Users/erichblume/.config/borgmatic - /Users/erichblume/Documents + - /Users/erichblume/.local/share/borgmatic/k8s-dumps # Backup repositories borgmatic_repositories: @@ -31,6 +32,19 @@ borgmatic_repositories: # BorgBase SSH key (fetched from 1Password in playbook pre_tasks) borgmatic_borgbase_ssh_key_path: /Users/erichblume/.ssh/borgbase_ed25519 +# Directory for pre-backup database dumps from k8s pods +borgmatic_k8s_dump_dir: /Users/erichblume/.local/share/borgmatic/k8s-dumps + +# K8s SQLite databases to dump before backup via kubectl exec +# Each entry runs: kubectl exec -- sqlite3 ".backup /tmp/backup.db" +# then copies the dump to borgmatic_k8s_dump_dir/.db +borgmatic_k8s_sqlite_dumps: + - name: mealie + namespace: mealie + label_selector: app=mealie + db_path: /app/data/mealie.db + context: minikube-indri + # Exclude patterns borgmatic_exclude_patterns: [] diff --git a/ansible/roles/borgmatic/tasks/main.yml b/ansible/roles/borgmatic/tasks/main.yml index ea82cb2..a4b1d7b 100644 --- a/ansible/roles/borgmatic/tasks/main.yml +++ b/ansible/roles/borgmatic/tasks/main.yml @@ -33,6 +33,13 @@ key: "u3ugi1x1.repo.borgbase.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGU0mISTyHBw9tBs6SuhSq8tvNM8m9eifQxM+88TowPO" state: present +- name: Ensure k8s dump directory exists + ansible.builtin.file: + path: "{{ borgmatic_k8s_dump_dir }}" + state: directory + mode: '0700' + when: borgmatic_k8s_sqlite_dumps | length > 0 + - name: Deploy borgmatic configuration ansible.builtin.template: src: config.yaml.j2 diff --git a/ansible/roles/borgmatic/templates/config.yaml.j2 b/ansible/roles/borgmatic/templates/config.yaml.j2 index 9b8da14..85804b7 100644 --- a/ansible/roles/borgmatic/templates/config.yaml.j2 +++ b/ansible/roles/borgmatic/templates/config.yaml.j2 @@ -31,6 +31,16 @@ exclude_patterns: encryption_passcommand: {{ borgmatic_encryption_passcommand }} +{% if borgmatic_k8s_sqlite_dumps %} +# Pre-backup: dump SQLite databases from k8s pods +# Uses sqlite3 .backup for a safe, consistent copy (no corruption from concurrent writes) +before_backup: + - mkdir -p {{ borgmatic_k8s_dump_dir }} +{% for db in borgmatic_k8s_sqlite_dumps %} + - /opt/homebrew/bin/kubectl --context={{ db.context }} exec -n {{ db.namespace }} deploy/{{ db.name }} -- python3 -c "import sqlite3; sqlite3.connect('{{ db.db_path }}').backup(sqlite3.connect('/tmp/{{ db.name }}-backup.db'))" && /opt/homebrew/bin/kubectl --context={{ db.context }} cp {{ db.namespace }}/$(/opt/homebrew/bin/kubectl --context={{ db.context }} get pod -n {{ db.namespace }} -l {{ db.label_selector }} -o jsonpath='{.items[0].metadata.name}'):/tmp/{{ db.name }}-backup.db {{ borgmatic_k8s_dump_dir }}/{{ db.name }}.db +{% endfor %} +{% endif %} + ssh_command: ssh -o IdentitiesOnly=yes -i {{ borgmatic_borgbase_ssh_key_path }} # Retention policy diff --git a/ansible/roles/caddy/defaults/main.yml b/ansible/roles/caddy/defaults/main.yml index a9576a1..dbf0b13 100644 --- a/ansible/roles/caddy/defaults/main.yml +++ b/ansible/roles/caddy/defaults/main.yml @@ -91,6 +91,9 @@ caddy_services: - name: ollama host: "ollama.{{ caddy_domain }}" backend: "https://ollama.tail8d86e.ts.net" + - name: mealie + host: "meals.{{ caddy_domain }}" + backend: "https://meals.tail8d86e.ts.net" - name: sifaka host: "nas.{{ caddy_domain }}" backend: "http://sifaka:5000" diff --git a/argocd/apps/mealie.yaml b/argocd/apps/mealie.yaml new file mode 100644 index 0000000..af33469 --- /dev/null +++ b/argocd/apps/mealie.yaml @@ -0,0 +1,17 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: mealie + namespace: argocd +spec: + project: default + source: + repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git + targetRevision: main + path: argocd/manifests/mealie + destination: + server: https://kubernetes.default.svc + namespace: mealie + syncPolicy: + syncOptions: + - CreateNamespace=true diff --git a/argocd/manifests/authentik/configmap-blueprint.yaml b/argocd/manifests/authentik/configmap-blueprint.yaml index f6ea4d6..cc3ff43 100644 --- a/argocd/manifests/authentik/configmap-blueprint.yaml +++ b/argocd/manifests/authentik/configmap-blueprint.yaml @@ -345,3 +345,47 @@ data: provider: !KeyOf jellyfin-provider meta_launch_url: https://jellyfin.ops.eblu.me policy_engine_mode: all + + mealie.yaml: | + version: 1 + metadata: + name: BlumeOps Mealie SSO + labels: + blueprints.goauthentik.io/description: "Mealie OIDC provider and application" + entries: + # OAuth2 provider for Mealie (confidential — Mealie requires client_secret) + - model: authentik_providers_oauth2.oauth2provider + id: mealie-provider + identifiers: + name: Mealie + attrs: + name: Mealie + authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] + invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] + client_type: confidential + client_id: mealie + client_secret: !Env AUTHENTIK_MEALIE_CLIENT_SECRET + redirect_uris: + - matching_mode: strict + url: https://meals.ops.eblu.me/login + - matching_mode: strict + url: https://meals.tail8d86e.ts.net/login + signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]] + property_mappings: + - !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]] + - !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]] + - !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]] + sub_mode: hashed_user_id + include_claims_in_id_token: true + + # Mealie application — all authenticated users allowed (admin mapped via OIDC_ADMIN_GROUP) + - model: authentik_core.application + id: mealie-app + identifiers: + slug: mealie + attrs: + name: Mealie + slug: mealie + provider: !KeyOf mealie-provider + meta_launch_url: https://meals.ops.eblu.me + policy_engine_mode: all diff --git a/argocd/manifests/authentik/deployment-worker.yaml b/argocd/manifests/authentik/deployment-worker.yaml index 2b341bf..3d4fb0c 100644 --- a/argocd/manifests/authentik/deployment-worker.yaml +++ b/argocd/manifests/authentik/deployment-worker.yaml @@ -78,6 +78,11 @@ spec: secretKeyRef: name: authentik-config key: argocd-client-secret + - name: AUTHENTIK_MEALIE_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: authentik-config + key: mealie-client-secret volumeMounts: - name: blueprints mountPath: /blueprints/custom diff --git a/argocd/manifests/authentik/external-secret.yaml b/argocd/manifests/authentik/external-secret.yaml index 495eda8..fb22f2b 100644 --- a/argocd/manifests/authentik/external-secret.yaml +++ b/argocd/manifests/authentik/external-secret.yaml @@ -57,3 +57,7 @@ spec: remoteRef: key: "Authentik (blumeops)" property: argocd-client-secret + - secretKey: mealie-client-secret + remoteRef: + key: "Authentik (blumeops)" + property: mealie-client-secret diff --git a/argocd/manifests/mealie/deployment.yaml b/argocd/manifests/mealie/deployment.yaml new file mode 100644 index 0000000..4eab901 --- /dev/null +++ b/argocd/manifests/mealie/deployment.yaml @@ -0,0 +1,79 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mealie + namespace: mealie +spec: + replicas: 1 + selector: + matchLabels: + app: mealie + template: + metadata: + labels: + app: mealie + spec: + containers: + - name: mealie + image: registry.ops.eblu.me/blumeops/mealie:kustomized + ports: + - containerPort: 9000 + env: + - name: BASE_URL + value: "https://meals.ops.eblu.me" + - name: ALLOW_SIGNUP + value: "false" + - name: TZ + value: "America/Los_Angeles" + - name: MAX_WORKERS + value: "1" + - name: WEB_CONCURRENCY + value: "1" + # OIDC — Authentik (public client, PKCE) + - name: OIDC_AUTH_ENABLED + value: "true" + - name: OIDC_CONFIGURATION_URL + value: "https://authentik.ops.eblu.me/application/o/mealie/.well-known/openid-configuration" + - name: OIDC_CLIENT_ID + value: "mealie" + - name: OIDC_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: mealie-oidc + key: client-secret + - name: OIDC_AUTO_REDIRECT + value: "false" + - name: OIDC_PROVIDER_NAME + value: "Authentik" + - name: OIDC_ADMIN_GROUP + value: "admins" + - name: OIDC_SIGNUP_ENABLED + value: "true" + - name: OIDC_USER_CLAIM + value: "email" + volumeMounts: + - name: data + mountPath: /app/data + resources: + requests: + memory: "128Mi" + cpu: "50m" + limits: + memory: "1000Mi" + cpu: "500m" + livenessProbe: + httpGet: + path: /api/app/about + port: 9000 + initialDelaySeconds: 30 + periodSeconds: 30 + readinessProbe: + httpGet: + path: /api/app/about + port: 9000 + initialDelaySeconds: 10 + periodSeconds: 10 + volumes: + - name: data + persistentVolumeClaim: + claimName: mealie-data diff --git a/argocd/manifests/mealie/external-secret.yaml b/argocd/manifests/mealie/external-secret.yaml new file mode 100644 index 0000000..6a77c5d --- /dev/null +++ b/argocd/manifests/mealie/external-secret.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: mealie-oidc + namespace: mealie +spec: + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: onepassword-blumeops + target: + name: mealie-oidc + creationPolicy: Owner + data: + - secretKey: client-secret + remoteRef: + key: "Authentik (blumeops)" + property: mealie-client-secret diff --git a/argocd/manifests/mealie/ingress-tailscale.yaml b/argocd/manifests/mealie/ingress-tailscale.yaml new file mode 100644 index 0000000..a885e15 --- /dev/null +++ b/argocd/manifests/mealie/ingress-tailscale.yaml @@ -0,0 +1,25 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: mealie-tailscale + namespace: mealie + annotations: + tailscale.com/proxy-class: "default" + tailscale.com/proxy-group: "ingress" + gethomepage.dev/enabled: "true" + gethomepage.dev/name: "Mealie" + gethomepage.dev/group: "Home" + gethomepage.dev/icon: "mealie.png" + gethomepage.dev/description: "Recipe manager" + gethomepage.dev/href: "https://meals.ops.eblu.me" + gethomepage.dev/pod-selector: "app=mealie" +spec: + ingressClassName: tailscale + defaultBackend: + service: + name: mealie + port: + number: 9000 + tls: + - hosts: + - meals diff --git a/argocd/manifests/mealie/kustomization.yaml b/argocd/manifests/mealie/kustomization.yaml new file mode 100644 index 0000000..0d1ee04 --- /dev/null +++ b/argocd/manifests/mealie/kustomization.yaml @@ -0,0 +1,15 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: mealie + +resources: + - deployment.yaml + - service.yaml + - pvc.yaml + - ingress-tailscale.yaml + - external-secret.yaml + +images: + - name: registry.ops.eblu.me/blumeops/mealie + newTag: v3.12.0-5c5fd18 diff --git a/argocd/manifests/mealie/pvc.yaml b/argocd/manifests/mealie/pvc.yaml new file mode 100644 index 0000000..f473e07 --- /dev/null +++ b/argocd/manifests/mealie/pvc.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: mealie-data + namespace: mealie +spec: + accessModes: + - ReadWriteOnce + storageClassName: standard + resources: + requests: + storage: 2Gi diff --git a/argocd/manifests/mealie/service.yaml b/argocd/manifests/mealie/service.yaml new file mode 100644 index 0000000..4162b96 --- /dev/null +++ b/argocd/manifests/mealie/service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: mealie + namespace: mealie +spec: + selector: + app: mealie + ports: + - name: http + port: 9000 + targetPort: 9000 + protocol: TCP diff --git a/containers/mealie/Dockerfile b/containers/mealie/Dockerfile new file mode 100644 index 0000000..fe1bf02 --- /dev/null +++ b/containers/mealie/Dockerfile @@ -0,0 +1,142 @@ +# Mealie — self-hosted recipe manager +# Built from source via forge mirror of mealie-recipes/mealie +# Based on upstream docker/Dockerfile (multi-stage: Node frontend + Python backend) + +ARG CONTAINER_APP_VERSION=v3.12.0 + +############################################### +# Frontend Build +############################################### +FROM node:24-slim AS frontend-builder + +ARG CONTAINER_APP_VERSION +RUN apt-get update && apt-get install --no-install-recommends -y git ca-certificates && rm -rf /var/lib/apt/lists/* + +RUN git clone --depth 1 --branch ${CONTAINER_APP_VERSION} \ + https://forge.ops.eblu.me/mirrors/mealie.git /src + +WORKDIR /src/frontend + +RUN yarn install \ + --prefer-offline \ + --frozen-lockfile \ + --non-interactive \ + --production=false \ + --network-timeout 1000000 + +RUN yarn generate + +############################################### +# Python Base +############################################### +FROM python:3.12-slim AS python-base + +ENV MEALIE_HOME="/app" +ENV PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + PIP_NO_CACHE_DIR=off \ + PIP_DISABLE_PIP_VERSION_CHECK=on \ + PIP_DEFAULT_TIMEOUT=100 \ + VENV_PATH="/opt/mealie" + +ENV PATH="$VENV_PATH/bin:$PATH" + +RUN useradd -u 911 -U -d $MEALIE_HOME -s /bin/bash abc \ + && usermod -G users abc \ + && mkdir $MEALIE_HOME + +############################################### +# Backend Package Build +############################################### +FROM python-base AS backend-builder + +ARG CONTAINER_APP_VERSION +RUN apt-get update \ + && apt-get install --no-install-recommends -y curl git ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +RUN pip install uv + +RUN git clone --depth 1 --branch ${CONTAINER_APP_VERSION} \ + https://forge.ops.eblu.me/mirrors/mealie.git /src + +WORKDIR /src + +COPY --from=frontend-builder /src/frontend/dist ./mealie/frontend + +RUN uv build --out-dir dist + +RUN uv export --no-editable --no-emit-project --extra pgsql --format requirements-txt --output-file dist/requirements.txt \ + && MEALIE_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])") \ + && echo "mealie[pgsql]==${MEALIE_VERSION} \\" >> dist/requirements.txt \ + && pip hash dist/mealie-${MEALIE_VERSION}-py3-none-any.whl | tail -n1 | tr -d '\n' >> dist/requirements.txt \ + && echo " \\" >> dist/requirements.txt \ + && pip hash dist/mealie-${MEALIE_VERSION}.tar.gz | tail -n1 >> dist/requirements.txt + +############################################### +# Python Venv Build +############################################### +FROM python-base AS venv-builder + +RUN apt-get update \ + && apt-get install --no-install-recommends -y \ + build-essential \ + libpq-dev \ + libwebp-dev \ + ffmpeg \ + libsasl2-dev libldap2-dev libssl-dev \ + gnupg gnupg2 gnupg1 \ + && rm -rf /var/lib/apt/lists/* + +RUN python3 -m venv --upgrade-deps $VENV_PATH + +COPY --from=backend-builder /src/dist /dist + +RUN . $VENV_PATH/bin/activate \ + && pip install --require-hashes -r /dist/requirements.txt --find-links /dist + +############################################### +# Production Image +############################################### +FROM python-base AS production + +ENV PRODUCTION=true +ENV TESTING=false + +RUN apt-get update \ + && apt-get install --no-install-recommends -y \ + curl \ + ffmpeg \ + gosu \ + iproute2 \ + libldap-common \ + libldap2 \ + && rm -rf /var/lib/apt/lists/* + +RUN mkdir -p /run/secrets + +COPY --from=venv-builder $VENV_PATH $VENV_PATH + +ENV NLTK_DATA="/nltk_data/" +RUN mkdir -p $NLTK_DATA +RUN python -m nltk.downloader -d $NLTK_DATA averaged_perceptron_tagger_eng + +VOLUME ["$MEALIE_HOME/data/"] +ENV APP_PORT=9000 + +EXPOSE ${APP_PORT} + +COPY --from=backend-builder /src/docker/healthcheck.sh $MEALIE_HOME/healthcheck.sh +RUN chmod +x $MEALIE_HOME/healthcheck.sh +HEALTHCHECK CMD $MEALIE_HOME/healthcheck.sh + +ENV HOST=0.0.0.0 + +COPY --from=backend-builder /src/docker/entry.sh $MEALIE_HOME/run.sh +RUN chmod +x $MEALIE_HOME/run.sh + +LABEL org.opencontainers.image.title="Mealie" +LABEL org.opencontainers.image.description="Self-hosted recipe manager" +LABEL org.opencontainers.image.source="https://github.com/mealie-recipes/mealie" + +ENTRYPOINT ["/app/run.sh"] diff --git a/docs/changelog.d/deploy-mealie.feature.md b/docs/changelog.d/deploy-mealie.feature.md new file mode 100644 index 0000000..5b85426 --- /dev/null +++ b/docs/changelog.d/deploy-mealie.feature.md @@ -0,0 +1 @@ +Deploy Mealie recipe manager on minikube-indri for meal planning and prep automation. diff --git a/docs/explanation/federated-login.md b/docs/explanation/federated-login.md index 8accad0..e576d9f 100644 --- a/docs/explanation/federated-login.md +++ b/docs/explanation/federated-login.md @@ -76,11 +76,12 @@ Authentik enforces TOTP MFA on its default authentication flow (`not_configured_ ## Future Work -- **Additional services:** ArgoCD, Miniflux, Immich +- **Additional services:** Miniflux, Immich ## Related - [[authentik]] - OIDC identity provider reference - [[grafana]] - First OIDC client +- [[mealie]] - Recipe manager (public PKCE client) - [[security-model]] - Network security and access control - [[deploy-authentik]] - Deployment how-to diff --git a/docs/reference/infrastructure/routing.md b/docs/reference/infrastructure/routing.md index 91457e9..c85dbb5 100644 --- a/docs/reference/infrastructure/routing.md +++ b/docs/reference/infrastructure/routing.md @@ -40,6 +40,7 @@ DNS points to [[indri]]'s Tailscale IP. TLS via Let's Encrypt (ACME DNS-01 with | [[navidrome]] | https://dj.ops.eblu.me | Music streaming | | [[jellyfin]] | https://jellyfin.ops.eblu.me | Media server | | [[postgresql]] | pg.ops.eblu.me:5432 | Database | +| [[mealie]] | https://meals.ops.eblu.me | Recipe manager | | [[sifaka|Sifaka]] | https://nas.ops.eblu.me | NAS dashboard | ## Public Services (`*.eblu.me`) diff --git a/docs/reference/kubernetes/apps.md b/docs/reference/kubernetes/apps.md index f584d5a..270cc55 100644 --- a/docs/reference/kubernetes/apps.md +++ b/docs/reference/kubernetes/apps.md @@ -39,6 +39,7 @@ Registry of all applications deployed via [[argocd]]. | `cv` | cv | `argocd/manifests/cv/` | [[cv]] | | `forgejo-runner` | forgejo-runner | `argocd/manifests/forgejo-runner/` | [[forgejo]] CI | | `ollama` | ollama | `argocd/manifests/ollama/` | [[ollama]] | +| `mealie` | mealie | `argocd/manifests/mealie/` | [[mealie]] | ## Sync Policies diff --git a/docs/reference/services/borgmatic.md b/docs/reference/services/borgmatic.md index 05c851e..1020327 100644 --- a/docs/reference/services/borgmatic.md +++ b/docs/reference/services/borgmatic.md @@ -1,6 +1,6 @@ --- title: Borgmatic -modified: 2026-02-10 +modified: 2026-03-16 tags: - service - backup @@ -26,11 +26,15 @@ Daily backup system using Borg backup, running on indri. - `/opt/homebrew/var/forgejo` - Git forge data - `~/.config/borgmatic` - Borgmatic config - `~/Documents` - Personal documents +- `~/.local/share/borgmatic/k8s-dumps/` - SQLite dumps from k8s pods -**Databases:** +**PostgreSQL databases:** - `miniflux` on [[postgresql]] - `teslamate` on [[postgresql]] +**K8s SQLite databases (pre-backup dump via kubectl exec):** +- [[mealie]] - Recipe manager (`/app/data/mealie.db`) + **Not backed up (by design):** - ZIM archives (re-downloadable) - Prometheus metrics (ephemeral) diff --git a/docs/reference/services/mealie.md b/docs/reference/services/mealie.md new file mode 100644 index 0000000..f309a5e --- /dev/null +++ b/docs/reference/services/mealie.md @@ -0,0 +1,61 @@ +--- +title: Mealie +modified: 2026-03-16 +tags: + - service + - recipes +--- + +# Mealie + +Self-hosted recipe manager with a REST API. Part of the meal planning pipeline: Mealie stores categorized recipes, a planner script selects balanced meals, and [[ollama]] generates a unified cooking timeline. + +## Quick Reference + +| Property | Value | +|----------|-------| +| **URL** | https://meals.ops.eblu.me | +| **Tailscale URL** | https://meals.tail8d86e.ts.net | +| **Namespace** | `mealie` | +| **Image** | `registry.ops.eblu.me/blumeops/mealie` (built from source) | +| **Database** | SQLite (local, at `/app/data/`) | +| **API Docs** | https://meals.ops.eblu.me/docs | +| **Upstream** | https://github.com/mealie-recipes/mealie | +| **Manifests** | `argocd/manifests/mealie/` | + +## Features + +- Full REST API (FastAPI) for recipe CRUD, filtering by tag/category +- Structured recipe data: ingredients (quantity/unit/food), step-by-step instructions +- Built-in meal planning and shopping lists +- Recipe import from URLs +- API token auth for automation +- OIDC login via [[authentik]] (confidential client) + +## Authentication + +OIDC via [[authentik]] using a confidential client. Client secret stored in 1Password (`Authentik (blumeops)` / `mealie-client-secret`) and delivered via ExternalSecret. All Authentik users can log in; members of the `admins` group get Mealie admin privileges via `OIDC_ADMIN_GROUP`. + +## Storage + +- 2Gi PVC at `/app/data/` via `standard` storageClassName (minikube-hostpath) +- SQLite database (sufficient for single-user) +- Recipe images and assets stored alongside the database + +## Backup + +SQLite database backed up via [[borgmatic]]'s `before_backup` hook. Borgmatic runs `kubectl exec` to create a safe `.backup` copy (via Python's `sqlite3` module), then `kubectl cp` to the host. The dump lands in `~/.local/share/borgmatic/k8s-dumps/mealie.db` and is included in both local (sifaka) and offsite (BorgBase) backups. + +## Networking + +| Endpoint | Reachable from | +|----------|----------------| +| `https://meals.ops.eblu.me` | Tailnet clients (via Caddy) | +| `https://meals.tail8d86e.ts.net` | Tailnet clients | +| `http://mealie.mealie.svc.cluster.local:9000` | In-cluster | + +## Related + +- [[authentik]] — OIDC identity provider +- [[ollama]] — LLM backend for meal timeline generation +- [[borgmatic]] — Data backup diff --git a/service-versions.yaml b/service-versions.yaml index 686a529..7877eda 100644 --- a/service-versions.yaml +++ b/service-versions.yaml @@ -253,6 +253,13 @@ services: upstream-source: https://code.forgejo.org/forgejo/runner/releases notes: Forgejo runner on ringtail via nixpkgs; version tracks flake.lock + - name: mealie + type: argocd + last-reviewed: 2026-03-16 + current-version: "v3.12.0" + upstream-source: https://github.com/mealie-recipes/mealie/releases + notes: Recipe manager; built from source via forge mirror + - name: unpoller type: argocd last-reviewed: 2026-03-16