Cookbook attempts to be a modern and simple website to display recipes. This means among other things:
- Lightweight websites with minimal javascript
- Necessary ingredients displayed next to each step
- Multi-language support
(TODO images)
Cookbook is written as a WSGI service. It is recommended to run it via uWSGI, though several other deployment options are available. Please see the Flask deployment instructions for in-depth information.
Please find example setup scenarios below:
Clone this repository to some location, e.g. /usr/local/cookbook
. This means
you should find e.g. the following paths:
/usr/local/cookbook/README.md
/usr/local/cookbook/cookbook/main.py
/usr/local/cookbook/cookbook/static/
Create a virtual env in /usr/local/cookbook
, i.e.
python3 -m venv /usr/local/cookbook/env
Activate the virtual env and install cookbook into it:
source /usr/local/cookbook/env/bin/activate
python3 -m pip install .
Create /var/cookbook/
. In /var/cookbook/recipes/
, put your recipes; in
/var/cookbook/recipes/images/
, put your images.
In /var/cookbook/config.json
, put:
{
"COOKBOOK_LOCATION": "/var/cookbook/recipes",
"SECRET_KEY": "YOUR-SECRET-KEY",
"DEFAULT_LANG": "en",
"BASE_URL": "https://your.website.name",
"SITE_NAME": "Cookbook"
}
Replace all of these keys as necessary. SECRET_KEY
should be any randomly
generated string, see the Flask config for more details.
If you prefer, you can also set all of these options as environment variables. Simply
prefix them with FLASK_
(e.g. FLASK_COOKBOOK_LOCATION
). If both a config file
and environment variables are provided, environment variables take precedence.
Install uWSGI. Find (or create)
the configuration directory for uwsgi. We will assume /var/uwsgi
for this
tutorial.
In /var/uwsgi/server.json
, put
{
"uwsgi": {
"emperor": "/var/uwsgi/vassals"
}
}
In /var/uwsgi/vassals/cookbook.json
, put
{
"uwsgi": {
"chmod-socket": "664",
"chown-socket": "uwsgi:www-data",
"env": [
"PATH=/usr/local/cookbook/env/bin",
"COOKBOOK_CONFIG=/var/cookbook/config.json"
],
"master": true,
"module": "cookbook:app",
"plugin": "python3",
"plugins": ["python3"],
"pyhome": "/usr/local/cookbook/env",
"socket": "/run/uwsgi/cookbook.sock",
"workers": 5
}
}
Change the value of chown-socket
to the user that runs -- or will run -- the
uWSGI service and the group that runs -- or will run -- nginx service. In env
,
you can also provide configuration keys (see above for details).
Set up a systemd service (if none comes with your distro) to start uwsgi. This
service should run as the user set in chown-socket
. Start uwsgi with the option
--json /var/uwsgi/server.json
, for example:
[Service]
User=uwsgi
Group=uwsgi
ExecStart=uwsgi --json /var/uwsgi/server.json
# More options here
Enable and start the uwsgi service:
systemctl enable uwsgi
systemctl start uwsgi
Set up nginx. Amend the configuration for the server section where you intend to host the server as follows:
http {
include uwsgi_params;
server {
# other server config goes here...
server_name your.cookbook.url;
location / {
uwsgi_pass unix:/run/uwsgi/cookbook.sock;
}
location /images/ {
alias /var/cookbook/recipes/images/;
expires 7d;
add_header Cache-Control "public";
}
location /static/ {
alias /usr/local/cookbook/cookbook/static/;
}
}
}
Enable and start nginx:
systemctl enable nginx
systemctl start nginx
Navigate to the URL set in server_name
to verify that cookbook is deployed and
running.
While the default setup of Cookbook assumes that it is deployed at the top-level of a webserver, it is possible to host it in a subfolder.
To this end:
- Make sure that BASE_URL in the configuration file contains the full path to Cookbook's index (e.g.
https://example.com/cookbook/
) - Add the configuration key
APPLICATION_ROOT
specifiying the subfolder (e.g./cookbook/
) - Prefix the three locations in the nginx config with the subfolder (e.g.
location /cookbook/ { ... }
,location /cookbook/images/ { ... }
andlocation /cookbook/static/ { ... }
)
A nix module is being written. In the meantime, if you would like guidance on how to set up cookbook via nix, please see here for some hints.
Recipes are stored in yaml files. These files should all be stored in one folder,
which should be provided to the cookbook service in the config file (COOKBOOK_LOCATION
).
The file names should follow the following format: <id>.<language>.yml
or
<id>.<language>.yaml
. The cookbook service will automatically aggregate recipes
with the same id but different language codes, see 'Localization' for more
information.
Recipe ids exist in two variants: Unnormalized, as found in the file name, and
normalized, for internal handling (e.g. the recipe in Chocolate Cake.en.yml
would have an unnormalized id of Chocolate Cake
and a normalized id
of chocolate-cake
).
Whenever you as the user need to specify ids (e.g. for translations or for linking related recipes), it is recommended you use unnormalized ids. This makes recipes more readable for you, and allows the normalized format to change without breaking your recipes.
Recipes can have linked images. For now these are only displayed in the search results. Images are loaded from a subfolder of the recipe folder. Specifically, the image is looked up in the following order:
<recipe-folder>/images/<unnormalized id>.png
<recipe-folder>/images/<unnormalized id>.jpg
<recipe-folder>/images/<normalized id>.png
<recipe-folder>/images/<normalized id>.jpg
Note that the service performs no further processing of the image files, instead serving the files directly (in fact, it is recommended to configure your webserver such that the image folder is served without dispatching to the service). Therefore, images should be cropped to a 16:9 aspect ratio (the search listing scales the images to 250 x 141px) [n.b. subject to change].
Key | Description | Default | Mandatory? |
---|---|---|---|
name |
Name of the recipe, as displayed in the search results and on the recipe. | Yes | |
serves |
Number of servings in this recipe. | Yes | |
servings_unit |
The "kind" of servings this recipe produces. Usually readers assume that servings refers to the number of people that you can feed with the given amounts. Override this here if it does not apply, (e.g. servings is the number of muffins this recipe produces) | n/a | |
servings_increment |
The change in servings when readers press the + or - buttons next to the servings. | 1 | |
descr |
A brief (one to two sentence) description of the recipe, displayed in the search results. | n/a | |
note |
Additional information about the recipe, displayed on the recipe page (e.g. suitable side dishes, possible variations). | ||
hide_from_all |
Hide the recipe from the 'all recipes' overview page. The recipe will still appear in more specific searches. | ||
related |
List of (normalized or unnormalized, see 'Recipe Ids') ids of related recipes. | [] | |
tags |
List or comma separated string of tags | [] | |
prep |
List of recipe steps (see 'Recipe Steps') for the prep stage: Things that can or should be prepared in advance. | [] | |
mis_en_place |
List of recipe steps for the mis-en-place phase: Preparing ingredients for assembly | [] | |
cooking |
List of recipe steps for the cooking phase: Active or semi-active assembly | [] | |
passive_cooking |
List of recipe steps for the passive cooking phase: Extended hands-off cooking times | [] | |
cooking2 |
List of recipe steps for the second cooking phase: Active or semi-active steps following a passive cooking phase | [] | |
passive_cooking2 |
List of recipe steps for the second passive cooking: Extended hands-off cooking times following a previous passive cooking phase | [] |
Key | Description | Default | Mandatory? |
---|---|---|---|
ingredients |
List of ingredients (see 'Ingredients') for this step | [] | |
internal_ingredients |
List of strings: Intermediate results (see yields field here) from previous steps, for semantic linking of steps. Displayed in the instructions. |
[] | |
hidden_ingredients |
List of strings: As internal_ingredients but only for semantic linking of steps, not displayed in the instructions. |
[] | |
yields |
String or list of strings: Intermediate result(s) produced by this step. Not mandatory, but recommend for all but the final step. | n/a | |
instructions |
Preparatory instructions for this step (we recommend yaml multiline strings, see examples for more information). | Yes |
Key | Description | Default | Mandatory? |
---|---|---|---|
ingredient |
Name of the ingredient | Yes | |
amount |
Amount of the ingredient to use (just the number, see unit field for unit information) |
n/a | |
unit |
Unit that the amount is given in |
The service attempts to serve one localized version of the website for each
language for which a recipe has been found. The recipe localization itself is
left up to the user. The website UI is localized with strings taken from
localization/<lang>.yml
. If the language has no localization file, or a
localization key is not defined, the service first falls back on the DEFAULT_LANG
language (definable in the config, if undefined defaults to the language
with the most recipes), and barring that, English.
The default language of the website is DEFAULT_LANG
. Specific languages can be
requested by prefixing all paths on the website by the desired language code.
Additionally, all recipes will link to versions of the same recipe in different
languages.
The service attempts to remember the preferred (i.e. last-used) language and serve that as a default in the future (needs work, might get removed in favor of explicit language paths everywhere except the default landing page)