Compare commits

...

4 Commits

Author SHA1 Message Date
unknown 1677eaeaec set correct date on cooklang self-host post
continuous-integration/drone/push Build is passing Details
2 months ago
unknown 92953c18e0 Tweak description for cooklang self-host post
continuous-integration/drone/push Build is passing Details
2 months ago
unknown 236b102281 New blog post about cooklang server
continuous-integration/drone/push Build was killed Details
2 months ago
unknown 26bca79108 drafts for vaesen retrospective
continuous-integration/drone/push Build is passing Details
2 months ago

@ -0,0 +1,103 @@
---
draft: false
title: "Self-hosting a Cooklang Server via systemd"
aliases: ["Self-hosting a Cooklang Server witvia systemd" ]
series: []
date: "2024-09-25"
author: "Nick Dumas"
cover: ""
keywords: ["", ""]
summary: "Hosting your own cooklang server is (mostly) easy with the help of systemd and some intermediate-level service configuration."
showFullContent: false
tags:
- systemd
- 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.
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
Starting the cooklang server is very straightforward: `cook server`, or `cook server path/to/recipes/`. This isn't very sustainable long-term, though, we don't want to be responsible for manually restarting the `server` process when it crashes for example. To handle that, we'll use a process supervisor which on most modern Linux systems will be `systemd` by default.
`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.
```
[Unit]
# Describe your unit. This is displayed in various system utilities for human administrators to understand what they're looking at.
Description="cooklang web server"
# The After parameter can be repeated and tells systemd that this unit file MUST NOT be started until the named resources are successfully made available.
# The syslog target guarantees that logs emitted by the cooklang server can be collected.
After=syslog.target
# The network target is available when the OS's network stack comes up. Being a web server, it doesn't really make sense to start until we can talk to other computers.
After=network.target
[Service]
# This throttles how quickly the service will be restarted. systemd will only attempt to restart the cooklang server once every 2 seconds. For applications that induce heavy load on the host machine during startup, this can be critical for preventing death spirals or otherwise degrading the performance of other services.
RestartSec=2s
# This is probably the most complex parameter in the whole file. For most processes, simple will do. I recommend reading through the documentation ( https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#Type= ) for more info.
Type=simple
# Tells systemd what user the process should be started as. Remember, anything that communicates with the outside world should be run as a standalone user with minimal priveleges; if the process is compromised, this limits the scope of damage malicious actors can cause.
User=cook
# Similar to the user parameter. UNIX groups allow different users to share access to a limited set of resources.
Group=cook
# WorkingDirectory effectively tells systemd to cd into the specified directory before executing the process.
WorkingDirectory=/home/cook/recipes/
# Here we tell systemd how to actually start the supervised process.
ExecStart=cook server
# The Restart parameter tells systemd how to handle restarting the process when it fails or is asked to restart by an operator. always means "Ignore exit codes and other signals and restart the process when asked, no matter what happens". For details on the other options, see https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#Restart=
Restart=always
[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=multi-user.target
```
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:
```
cook.ndumas.com {
encode gzip
reverse_proxy localhost:9080
}
```
### 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.
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.
#### `cooklang-watcher.service`
```
[Unit]
Description=cooklang server restarter
After=network.target
# The next two parameters work in tandem to prevent the service file from being triggered too many times, whatever that may mean for a given process. During each span of 10 seconds, systemd will only attempt to restart the process 5 times. Much like Restart in cooklang.service, this is to prevent death-spiraling and degradation of other processes on the host.
StartLimitIntervalSec=10
StartLimitBurst=5
[Service]
# oneshot is similar to simple but it considers the unit "up"/available after the process exits. Use this when the process you're running is not meant to persist.
Type=oneshot
ExecStart=/usr/bin/systemctl restart cooklang.service
[Install]
WantedBy=multi-user.target
```
#### `cooklang-watcher.path`
```
[Unit]
Description="Monitor /home/cook/recipes for changes"
[Path]
# This tells systemd which service to trigger when the configured file events are detected. Here, we're telling it "Trigger cooklang-watcher.service when you detect changes to /home/cook/recipes"
Unit=cooklang-watcher.service
# PathModified is one of several subtle variations that tell systemd what directory/file to watch and what type of events to care about. PathModified catches more events than PathChanged which is important because I'll frequently be editing these files interactively in vim as opposed to say, a CI/CD system which will do big batches of simple writes.
# For more info, see the docs https://www.freedesktop.org/software/systemd/man/latest/systemd.path.html#PathExists=
PathModified=/home/cook/recipes
[Install]
WantedBy=multi-user.target
```
### 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.

@ -16,7 +16,9 @@ showFullContent: false
{{< lead >}} The dreamer cannot remember. {{</ lead >}}
## Disclaimers and Content Warning
1. This post contains references to murder, gruesome deaths, and the supernatural.
2. I'm going to try to avoid spoiling the contents of the module we played.
2. I'm going to keep descriptions of the module we played and system mechanics broad. Please consider buying Vaesen and its modules to support the creators.
3. I'm going to be pretty critical of Alchemy VTT. I am a not unbiased because I'm a long-time Foundry user and overall a big fan of its design and pricing model. I feel that my criticisms stand on their own.
## The Prep
Preparing for Vaesen was extremely interesting. Character creation presents an interesting tradeoff early on when you decide your character's age. This choice impacts two things: your attributes and your skills. The younger you are, the more points you can allocate to to your attributes but you get fewer skill points. The reverse applies for older characters: you've had longer to practice your skills but your body just isn't what it used to be.
@ -27,6 +29,14 @@ Before your first mission, you can purchase equipment ( knives, tinderboxes, hun
Speaking of, the setting for Vaesen is stunning and it sets the mind alight with possibility. The dawn of the Industrial Age spurs sudden population booms in urban centers which begin expanding outward into the wilderness while folk of all sorts are drawn from the countryside in search of gainful labor or to chase their dreams of riches. Forests are cleared to fuel and build machines that belch smoke into the sky, ore is ripped from the living earth in the name of growth and progress, and all the while people quietly forget or proudly forsake The Old ways. All the while, the creatures and forces humankind once carefully and respectfully shared the world with grow disquiet, no longer appeased by rituals and mindful distance.
A rare few humans gain The Sight, the ability to directly witness the strange and powerful forces that move just behind the veil of myth. The Sight should not be called a gift generally being found only after witnessing or experiencing some terrible tragedy, but it grants the ability to intervene in or avoid the activity of these supernatural beings, the Vaesen. If you had the sight, if you were a Child of Thursday, there was a chance that you might encounter or be recruited by The Society. The Society worked to understand the Vaesen and their place in the world, their desires and needs, and to keep the peace between all who called Sweden home.
## The Game
- Castle Gyllencreutz, Upsala: your base of operations
- upgrading your base
- Investigation
- Conditions
- Physical and Mental
- Dice pools
- Dispelling or soothing the Vaesen
## The Venue
During the course of our game, we used two separate Virtual Table Tops to play: Alchemy and Foundry. We switched after our first session ( fact check this? maybe it was two ) because of some shortcomings with the current Alchemy implementation of the Vaesen system.
### Alchemy VTT
@ -49,19 +59,13 @@ Alchemy also did not go out of its way to make critical UI elements very visible
{{< figure src="alchemy_vaesen_precision_roll.png" alt="Three buttons are displayed, two are semi-transparent and barely legible against the background image: 'TEST' and 'EDIT'. The third is a white circle with a red X through the center." >}}
- Did not seem to have "token vision" or fog of war, or the pre-made module did not implement it (fact check)
Alchemy didn't seem to implement token vision or fog of war, at least not for the Vaesen system or the module we were using. This isn't the end of the world but for a **mystery game**, seeing the **entire map** the instant it loads can be immersion breaking at best and a meta-gaming nightmare at worst.
### Foundry
Our Foundry experience was a radical improvement. Our game-runner created our characters on our behalf so I can't speak to the process in that regard but the rest of the process was extremely straightforward. When rolling for skills the Vaesen system in Foundry had helpful prompts when you had equipment that might augment your dice pool.
- Foundry
- Purchasable System for mechanics
- Purchasable module for maps and NPCs
## The Game
- Castle Gyllencreutz, Upsala: your base of operations
- upgrading your base
- Investigation
- Conditions
- Physical and Mental
- Dice pools
- Dispelling or soothing the Vaesen
## The Tragedy
## Final Thoughts
Vaesen is not a game of heroic fantasy. Your characters are fragile and powerless to stop the events that play out with brute force. Any hope for intervention requires careful preparation and an immense amount of luck. Each injury makes future successes exponentially more unlikely. Failures cascade quickly. Your character *begins the game* scarred by something tragic in their past and will only be subject to a greater quantity and variety of violence and loss.

Loading…
Cancel
Save