From 3b524ec118e1a98da8bb4559905be566e4955fde Mon Sep 17 00:00:00 2001 From: dev_agent Date: Mon, 5 Oct 2026 08:08:16 -0400 Subject: [PATCH] Compose stack and env template (#54) Co-authored-by: dev_agent --- .gitignore | 2 ++ deploy/.env.example | 80 +++++++++++++++++++++++++++++++++++++++++++++ deploy/compose.yaml | 61 ++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 deploy/.env.example create mode 100644 deploy/compose.yaml diff --git a/.gitignore b/.gitignore index 60709ee..7f9b8de 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ design/.DS_Store # Secrets and local environment .env* +!deploy/.env.example +deploy/.env deploy/secrets/ # Build artifacts and dependencies diff --git a/deploy/.env.example b/deploy/.env.example new file mode 100644 index 0000000..d0e969e --- /dev/null +++ b/deploy/.env.example @@ -0,0 +1,80 @@ +# MealQuest production environment. Copy to deploy/.env (never committed) +# and replace the placeholders. The service fails fast: a missing or invalid +# required variable stops it at boot with the variable named. +# +# Secrets are never set here. They live in deploy/secrets/ (never committed), +# one value per file, owned by the container user and readable only by it: +# +# mkdir -p deploy/secrets && chmod 700 deploy/secrets +# head -c 32 /dev/urandom | base64 > deploy/secrets/secret_key +# printf '%s\n' '' > deploy/secrets/oidc_client_secret +# sudo chown 10001:10001 deploy/secrets/* && sudo chmod 600 deploy/secrets/* +# +# deploy/compose.yaml mounts them at /run/secrets/ and points the +# *_FILE variables at them. + +# --- Compose ----------------------------------------------------------------- + +# Host address the service is published on, as
:8090. Production +# binds the LAN address 10.60.1.14 only; 127.0.0.1 suits a dev machine. +MEALQUEST_BIND_ADDRESS=127.0.0.1 + +# --- Service ----------------------------------------------------------------- + +# Public origin the household opens. Fixes the OIDC redirect URI +# (/auth/callback) and the Secure cookies. Must be https:// (plain +# http:// is accepted only for localhost, 127.0.0.1 and [::1]). +MEALQUEST_PUBLIC_URL=https://mealquest.cubecraftlabs.com + +# How long a graceful shutdown may take before in-flight requests are cut. +MEALQUEST_SHUTDOWN_TIMEOUT=10s + +# Optional. Comma-separated IPs/CIDRs of reverse proxies whose +# X-Forwarded-For/-Proto headers are honoured. Production lists Traefik; +# unset trusts no peer. +MEALQUEST_TRUSTED_PROXY=10.60.1.33 + +# IANA zone the household settings are created with on first boot. Required +# until those settings exist; ignored afterwards (change the zone in +# household settings instead). Confirm the household's zone before the first +# deploy. +MEALQUEST_DEFAULT_TZ=America/New_York + +# --- Authentik OIDC ---------------------------------------------------------- + +# Issuer of the Authentik application "mealquest". Discovered at boot, so it +# must be reachable from the container. Must be https://. +MEALQUEST_OIDC_ISSUER=https://auth.cubecraftlabs.com/application/o/mealquest/ +MEALQUEST_OIDC_CLIENT_ID=mealquest +# Comma-separated Authentik groups whose members may sign in. +MEALQUEST_OIDC_ALLOWED_GROUPS=mealquest-admin + +# Optional session lifetimes. A session ends after IDLE unused and always MAX +# after sign-in; MAX must be at least IDLE. Defaults: 720h and 4320h. +MEALQUEST_SESSION_IDLE=720h +MEALQUEST_SESSION_MAX=4320h + +# --- Set by deploy/compose.yaml; do not set here ----------------------------- +# +# MEALQUEST_HTTP_ADDR=:8080 +# MEALQUEST_DATA_DIR=/data +# MEALQUEST_SECRET_KEY_FILE=/run/secrets/secret_key +# Base64 of 32 random bytes; encrypts secrets at rest (AES-256-GCM). +# MEALQUEST_OIDC_CLIENT_SECRET_FILE=/run/secrets/oidc_client_secret +# The Authentik provider's client secret. + +# --- Planned; not read by the service yet ------------------------------------- +# +# Added to deploy/compose.yaml and this file by the features that use them. +# +# Web Push (Feature 19). Generate the key pair with cmd/vapidgen. +# MEALQUEST_VAPID_PUBLIC_KEY= +# MEALQUEST_VAPID_PRIVATE_KEY_FILE=/run/secrets/vapid_private_key +# MEALQUEST_VAPID_SUBJECT=mailto: +# +# Google Calendar OAuth web client (Feature 16). +# MEALQUEST_GOOGLE_CLIENT_ID=.apps.googleusercontent.com +# MEALQUEST_GOOGLE_CLIENT_SECRET_FILE=/run/secrets/google_client_secret +# +# SearXNG recipe search sidecar on the compose network (Feature 07). +# MEALQUEST_SEARXNG_URL=http://searxng:8080 diff --git a/deploy/compose.yaml b/deploy/compose.yaml new file mode 100644 index 0000000..377e580 --- /dev/null +++ b/deploy/compose.yaml @@ -0,0 +1,61 @@ +# Production stack for MealQuest on 10.60.1.14 (D-018). Build and start on +# the host with: +# +# docker compose -f deploy/compose.yaml up -d --build +# +# Configuration comes from deploy/.env (copy deploy/.env.example) and secrets +# from deploy/secrets/; neither is committed. Compose bind-mounts file secrets +# with their host owner and mode, so each file in deploy/secrets/ must be +# owned by the container user 10001:10001 with mode 0600. +name: mealquest + +services: + mealquest: + build: + context: .. + dockerfile: Dockerfile + user: "10001:10001" + restart: unless-stopped + env_file: + - path: .env + required: true + # Fixed by this file's container shape (listen port, volume, secret + # mounts); deploy/.env must not set them. + environment: + MEALQUEST_HTTP_ADDR: ":8080" + MEALQUEST_DATA_DIR: /data + MEALQUEST_SECRET_KEY_FILE: /run/secrets/secret_key + MEALQUEST_OIDC_CLIENT_SECRET_FILE: /run/secrets/oidc_client_secret + secrets: + - secret_key + - oidc_client_secret + volumes: + - mealquest-data:/data + ports: + - "${MEALQUEST_BIND_ADDRESS:?set MEALQUEST_BIND_ADDRESS in deploy/.env}:8090:8080" + networks: + - mealquest + healthcheck: + test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8080/health"] + interval: 30s + timeout: 3s + start_period: 10s + retries: 3 + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + +secrets: + secret_key: + file: ./secrets/secret_key + oidc_client_secret: + file: ./secrets/oidc_client_secret + +volumes: + mealquest-data: + name: mealquest-data + +networks: + mealquest: + name: mealquest