summary: "Hosting your own cooklang server is (mostly) easy with the help of systemd and some intermediate-level service configuration."
summary: "Hosting your own cooklang server is (mostly) easy with the help of systemd and some intermediate-level service configuration."
showFullContent: false
showFullContent: false
tags:
tags:
- systemd
- systemd
- cooklang
- cooklang
---
---
## Cooklang
## Cooklang
Recently I became aware that [cooklang](https://cooklang.org)'s CLI has a built in `server` [subcommand](https://cooklang.org/cli/help/#server), giving you a web interface for viewing your recipes and generating shopping lists.
Recently I became aware that [cooklang](https://cooklang.org)'s CLI has a built in `server` [subcommand](https://cooklang.org/cli/help/#server), giving you a web interface for viewing your recipes and generating shopping lists.
Up until now, I've kept my recipes in [Obsidian](https://obsidian.md) which is great but Markdown has its limitations. The value-add for using Cooklang and its builtin web server is that I can easily build shopping lists for recipes and share them with friends and loved ones with a simple direct link.
Up until now, I've kept my recipes in [Obsidian](https://obsidian.md) which is great but Markdown has its limitations. The value-add for using Cooklang and its builtin web server is that I can easily build shopping lists for recipes and share them with friends and loved ones with a simple direct link.
## The systemd unit
## The systemd unit
@ -22,7 +22,7 @@ Starting the cooklang server is very straightforward: `cook server`, or `cook se
`systemd` uses [unit files](https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html) to configure resources it's responsible for. There's a lot going on in unit files so I've added as many comments as I could to explain what each lines does. Here, we're configuring a [service](https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html) which specifically describes a *process* that systemd is responsible for maintaining.
`systemd` uses [unit files](https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html) to configure resources it's responsible for. There's a lot going on in unit files so I've added as many comments as I could to explain what each lines does. Here, we're configuring a [service](https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html) which specifically describes a *process* that systemd is responsible for maintaining.
{{<highlightini>}}
```
[Unit]
[Unit]
# Describe your unit. This is displayed in various system utilities for human administrators to understand what they're looking at.
# Describe your unit. This is displayed in various system utilities for human administrators to understand what they're looking at.
Description="cooklang web server"
Description="cooklang web server"
@ -51,22 +51,22 @@ Restart=always
[Install]
[Install]
# WantedBy is part of systemd's dependency-chain configuration. multi-user.target is marked as available when the kernel/OS pass from single-user mode to multi-user mode, effectively meaning that the system is ready to start doing stuff other than bootstrapping the hardware and kernel. In short, "when the machine boots up, start me".
# WantedBy is part of systemd's dependency-chain configuration. multi-user.target is marked as available when the kernel/OS pass from single-user mode to multi-user mode, effectively meaning that the system is ready to start doing stuff other than bootstrapping the hardware and kernel. In short, "when the machine boots up, start me".
WantedBy=multi-user.target
WantedBy=multi-user.target
{{</highlight>}}
```
With this unit file in place, my [recipes site](https://cook.ndumas.com) comes to life, with the help of a new block in my caddyfile:
With this unit file in place, my [recipes site](https://cook.ndumas.com) comes to life, with the help of a new block in my caddyfile:
{{<highlightCaddyfile>}}
```
cook.ndumas.com {
cook.ndumas.com {
encode gzip
encode gzip
reverse_proxy localhost:9080
reverse_proxy localhost:9080
}
}
{{</highlight>}}
```
### Cooklang can't handle new recipe files
### Cooklang can't handle new recipe files
Unfortunately, there's a catch. It looks like the cooklang server can't gracefully handle the addition of new recipes; the UI will display the recipe name but throw an error about invalid JSON when you attempt to navigate to that recipe's page. This seems like a pretty egregious bug/oversight but luckily, systemd is exceedingly clever and already has [a solution](https://www.freedesktop.org/software/systemd/man/latest/systemd.path.html) for this baked in. Using systemd path units, you can tell systemd to perform actions based on the existence or modification/deletion of specified files or directories.
Unfortunately, there's a catch. It looks like the cooklang server can't gracefully handle the addition of new recipes; the UI will display the recipe name but throw an error about invalid JSON when you attempt to navigate to that recipe's page. This seems like a pretty egregious bug/oversight but luckily, systemd is exceedingly clever and already has [a solution](https://www.freedesktop.org/software/systemd/man/latest/systemd.path.html) for this baked in. Using systemd path units, you can tell systemd to perform actions based on the existence or modification/deletion of specified files or directories.
The full solution involves creating two additional unit-files: `cooklang-watcher.service` and `cooklang-watcher.path`. As above, I'll annotate the (new) directives to explain their functionality.
The full solution involves creating two additional unit-files: `cooklang-watcher.service` and `cooklang-watcher.path`. As above, I'll annotate the (new) directives to explain their functionality.
Description="Monitor /home/cook/recipes for changes"
Description="Monitor /home/cook/recipes for changes"
@ -97,7 +97,7 @@ PathModified=/home/cook/recipes
[Install]
[Install]
WantedBy=multi-user.target
WantedBy=multi-user.target
{{</highlight>}}
```
### Conclusion
### Conclusion
This is by far the most advanced `systemd` setup I've rolled from scratch and I'm really pleased with how it came together. I don't know if I'll stick with cooklang long-term but from an administrator/operator perspective the tools offered everything I needed to handle all the edge cases. Now I can start converting my recipes from Markdown.
This is by far the most advanced `systemd` setup I've rolled from scratch and I'm really pleased with how it came together. I don't know if I'll stick with cooklang long-term but from an administrator/operator perspective the tools offered everything I needed to handle all the edge cases. Now I can start converting my recipes from Markdown.