summaryrefslogtreecommitdiff
path: root/gemfeed
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-14 23:21:53 +0200
committerPaul Buetow <paul@buetow.org>2026-02-14 23:21:53 +0200
commit372e68ea8517a82f1d15bb83cf84962b971e41ea (patch)
tree9fa712a65e9d8b00b64654549bc67375bed95c40 /gemfeed
parent8bc73bfdbd2b4f7a167baf3cd075dc07e89d6cd4 (diff)
Update content for html
Diffstat (limited to 'gemfeed')
-rw-r--r--gemfeed/2025-06-22-task-samurai.html1
-rw-r--r--gemfeed/2025-08-05-local-coding-llm-with-ollama.html1
-rw-r--r--gemfeed/2026-02-14-meta-slash-commands-for-prompts-and-context.html1
-rw-r--r--gemfeed/2026-02-14-til-meta-slash-commands-for-ai-workflows.html110
-rw-r--r--gemfeed/atom.xml460
-rw-r--r--gemfeed/index.html1
6 files changed, 353 insertions, 221 deletions
diff --git a/gemfeed/2025-06-22-task-samurai.html b/gemfeed/2025-06-22-task-samurai.html
index 48246de3..9e10795d 100644
--- a/gemfeed/2025-06-22-task-samurai.html
+++ b/gemfeed/2025-06-22-task-samurai.html
@@ -145,7 +145,6 @@
<br />
<span>Other related posts are:</span><br />
<br />
-<a class='textlink' href='./2026-02-14-til-meta-slash-commands-for-ai-workflows.html'>2026-02-14 TIL: Meta slash-commands for reusable AI prompts and context</a><br />
<a class='textlink' href='./2025-08-05-local-coding-llm-with-ollama.html'>2025-08-05 Local LLM for Coding with Ollama on macOS</a><br />
<a class='textlink' href='./2025-06-22-task-samurai.html'>2025-06-22 Task Samurai: An agentic coding learning experiment (You are currently reading this)</a><br />
<br />
diff --git a/gemfeed/2025-08-05-local-coding-llm-with-ollama.html b/gemfeed/2025-08-05-local-coding-llm-with-ollama.html
index 076ce21e..f2a9d99d 100644
--- a/gemfeed/2025-08-05-local-coding-llm-with-ollama.html
+++ b/gemfeed/2025-08-05-local-coding-llm-with-ollama.html
@@ -466,7 +466,6 @@ content = "{CODE}"
<br />
<span>Other related posts are:</span><br />
<br />
-<a class='textlink' href='./2026-02-14-til-meta-slash-commands-for-ai-workflows.html'>2026-02-14 TIL: Meta slash-commands for reusable AI prompts and context</a><br />
<a class='textlink' href='./2025-08-05-local-coding-llm-with-ollama.html'>2025-08-05 Local LLM for Coding with Ollama on macOS (You are currently reading this)</a><br />
<a class='textlink' href='./2025-06-22-task-samurai.html'>2025-06-22 Task Samurai: An agentic coding learning experiment</a><br />
<br />
diff --git a/gemfeed/2026-02-14-meta-slash-commands-for-prompts-and-context.html b/gemfeed/2026-02-14-meta-slash-commands-for-prompts-and-context.html
index 38934e1b..b9aa949d 100644
--- a/gemfeed/2026-02-14-meta-slash-commands-for-prompts-and-context.html
+++ b/gemfeed/2026-02-14-meta-slash-commands-for-prompts-and-context.html
@@ -235,7 +235,6 @@
<br />
<span>Other related posts:</span><br />
<br />
-<a class='textlink' href='./2026-02-14-til-meta-slash-commands-for-ai-workflows.html'>2026-02-14 TIL: Meta slash-commands for reusable AI prompts and context</a><br />
<a class='textlink' href='./2026-02-14-meta-slash-commands-for-prompts-and-context.html'>2026-02-14 Meta slash-commands to manage prompts and context for coding agents (You are currently reading this)</a><br />
<a class='textlink' href='./2026-02-02-tmux-popup-editor-for-cursor-agent-prompts.html'>2026-02-02 A tmux popup editor for Cursor Agent CLI prompts</a><br />
<br />
diff --git a/gemfeed/2026-02-14-til-meta-slash-commands-for-ai-workflows.html b/gemfeed/2026-02-14-til-meta-slash-commands-for-ai-workflows.html
deleted file mode 100644
index 246d1d3f..00000000
--- a/gemfeed/2026-02-14-til-meta-slash-commands-for-ai-workflows.html
+++ /dev/null
@@ -1,110 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-<title>TIL: Meta slash-commands for reusable AI prompts and context</title>
-<link rel="shortcut icon" type="image/gif" href="/favicon.ico" />
-<link rel="stylesheet" href="../style.css" />
-<link rel="stylesheet" href="style-override.css" />
-</head>
-<body>
-<p class="header">
-<a href="https://foo.zone">Home</a> | <a href="https://codeberg.org/snonux/foo.zone/src/branch/content-md/gemfeed/2026-02-14-til-meta-slash-commands-for-ai-workflows.md">Markdown</a> | <a href="gemini://foo.zone/gemfeed/2026-02-14-til-meta-slash-commands-for-ai-workflows.gmi">Gemini</a>
-</p>
-<h1 style='display: inline' id='til-meta-slash-commands-for-reusable-ai-prompts-and-context'>TIL: Meta slash-commands for reusable AI prompts and context</h1><br />
-<br />
-<span class='quote'>Published at 2026-02-14T14:00:00+02:00</span><br />
-<br />
-<span>Short post in "This week I learned" style: what I tried, why it worked, and how you can replicate it. Format: Problem → Approach → Copy/paste → Result → Gotchas. Full reference for the meta-commands lives in a separate post.</span><br />
-<br />
-<a class='textlink' href='./2026-02-14-meta-slash-commands-for-prompts-and-context.html'>Full reference: Meta slash-commands to manage prompts and context for coding agents</a><br />
-<br />
-<h2 style='display: inline' id='til'>TIL</h2><br />
-<br />
-<h3 style='display: inline' id='problem'>Problem</h3><br />
-<br />
-<span>When I use a coding agent (Cursor Agent, Claude Code CLI, Ampcode, etc.), I repeat the same requests: "review this function," "explain this error," "add tests for this module," "format this as a blog post." Typing long prompts from scratch is tedious; ad-hoc prompts are easy to forget. I also keep pasting the same background (API conventions, project rules, infra notes) into every session so the agent doesn&#39;t guess blindly.</span><br />
-<br />
-<h3 style='display: inline' id='approach'>Approach</h3><br />
-<br />
-<span>Treat prompts and context as first-class artefacts: store them as markdown files (one per slash-command or per context) in a dotfiles repo. Use a small set of *meta* slash-commands so the agent itself creates, updates, and deletes these files from the conversation—no hand-editing. Two kinds of artefacts:</span><br />
-<br />
-<ul>
-<li>Commands — Reusable workflows (e.g. review-code, explain-error). Live in <span class='inlinecode'>commands/</span> as <span class='inlinecode'>.md</span> files.</li>
-<li>Context — Reusable background (API guidelines, runbooks, personas). Live in <span class='inlinecode'>context/</span> as <span class='inlinecode'>.md</span> files; you *load* one at session start so the agent has it in mind.</li>
-</ul><br />
-<h3 style='display: inline' id='copypaste'>Copy/paste</h3><br />
-<br />
-<span>Minimal workflow:</span><br />
-<br />
-<pre>
-/load-context api-guidelines
-</pre>
-<br />
-<span>Then ask the agent to implement a feature or fix a bug; it already has the guidelines.</span><br />
-<br />
-<span>To turn something you just did into a reusable command:</span><br />
-<br />
-<pre>
-/create-command review-code
-</pre>
-<br />
-<span>Full command reference (all meta-commands, parameters, examples):</span><br />
-<br />
-<a class='textlink' href='./2026-02-14-meta-slash-commands-for-prompts-and-context.html'>Meta slash-commands — full reference</a><br />
-<br />
-<h3 style='display: inline' id='result'>Result</h3><br />
-<br />
-<span>No more retyping long prompts; same prompt library across Cursor Agent, Claude Code CLI, OpenCode, Ampcode. Context is load-on-demand per session instead of pasting walls of text. Everything is versioned in git and synced across machines.</span><br />
-<br />
-<h3 style='display: inline' id='gotchas'>Gotchas</h3><br />
-<br />
-<ul>
-<li>Requires an agent that supports custom slash-commands (or reading prompt files from disk).</li>
-<li>Context is explicit: you must run <span class='inlinecode'>/load-context &lt;name&gt;</span> at session start; it&#39;s not implicit like some "skills" systems.</li>
-<li>A flat directory of commands can grow; if it gets unwieldy, consider grouping by skill or exposing via an MCP server later.</li>
-</ul><br />
-<h2 style='display: inline' id='beforeafter'>Before/After</h2><br />
-<br />
-<ul>
-<li>Before: Retyping or pasting long prompts each time; pasting API/project context into every session; prompts scattered and inconsistent across tools.</li>
-<li>After: <span class='inlinecode'>/load-context &lt;name&gt;</span> once per session; <span class='inlinecode'>/&lt;command&gt;</span> for repeatable tasks; commands and context in git, same library across agents.</li>
-</ul><br />
-<h2 style='display: inline' id='workflow-recipe'>Workflow recipe</h2><br />
-<br />
-<ul>
-<li>Steps: (1) Create or update context/commands via meta-commands when you have something worth reusing. (2) Start a session → run <span class='inlinecode'>/load-context &lt;name&gt;</span>. (3) Use slash-commands or ask ad-hoc; agent has background and consistent prompts.</li>
-<li>Tools: Cursor Agent (CLI), Claude Code CLI, OpenCode, Ampcode; markdown in e.g. <span class='inlinecode'>~/Notes/Prompts/commands/</span> and <span class='inlinecode'>~/Notes/Prompts/context/</span>.</li>
-<li>Impact: Saves retyping and keeps prompts consistent; one-time cost is creating the first context/command with the agent.</li>
-</ul><br />
-<h2 style='display: inline' id='micro-template-quick-reference'>Micro-template (quick reference)</h2><br />
-<br />
-<span>| Meta-command | Purpose | Good for |</span><br />
-<span>|--------------------|--------------------------------|-------------------------------------------|</span><br />
-<span>| /create-command | Create new slash-command | Turn current or recurring tasks into one |</span><br />
-<span>| /update-command | Edit existing slash-command | Refine after use |</span><br />
-<span>| /delete-command | Remove slash-command file | Clean up unused commands |</span><br />
-<span>| /create-context | Create new context file | Capture project/infra knowledge once |</span><br />
-<span>| /update-context | Edit existing context file | Keep context up to date |</span><br />
-<span>| /delete-context | Remove context file | Remove outdated context |</span><br />
-<span>| /load-context | Load context into conversation | Give agent background before tasks |</span><br />
-<br />
-<span>Example usage: <span class='inlinecode'>/load-context api-guidelines</span>, <span class='inlinecode'>/create-command review-code</span>, <span class='inlinecode'>/update-command review-code</span>. Full details and examples in the main post linked above.</span><br />
-<br />
-<span>Other related posts:</span><br />
-<br />
-<a class='textlink' href='./2026-02-14-meta-slash-commands-for-prompts-and-context.html'>2026-02-14 Meta slash-commands to manage prompts and context for coding agents</a><br />
-<a class='textlink' href='./2026-02-02-tmux-popup-editor-for-cursor-agent-prompts.html'>2026-02-02 A tmux popup editor for Cursor Agent CLI prompts</a><br />
-<br />
-<span>E-Mail your comments to <span class='inlinecode'>paul@nospam.buetow.org</span> :-)</span><br />
-<br />
-<a class='textlink' href='../'>Back to the main site</a><br />
-<p class="footer">
- Generated with <a href="https://codeberg.org/snonux/gemtexter">Gemtexter 3.0.1-develop</a> |
- served by <a href="https://www.OpenBSD.org">OpenBSD</a>/<a href="https://man.openbsd.org/relayd.8">relayd(8)</a>+<a href="https://man.openbsd.org/httpd.8">httpd(8)</a> |
- <a href="https://foo.zone/site-mirrors.html">Site Mirrors</a>
- <br />
- Webring: <a href="https://shring.sh/foo.zone/previous">previous</a> | <a href="https://shring.sh">shring</a> | <a href="https://shring.sh/foo.zone/next">next</a>
-</p>
-</body>
-</html>
diff --git a/gemfeed/atom.xml b/gemfeed/atom.xml
index 38e57456..3e681589 100644
--- a/gemfeed/atom.xml
+++ b/gemfeed/atom.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
- <updated>2026-02-14T23:11:16+02:00</updated>
+ <updated>2026-02-14T23:20:12+02:00</updated>
<title>foo.zone feed</title>
<subtitle>To be in the .zone!</subtitle>
<link href="https://foo.zone/gemfeed/atom.xml" rel="self" />
@@ -253,109 +253,6 @@ mage test
</content>
</entry>
<entry>
- <title>TIL: Meta slash-commands for reusable AI prompts and context</title>
- <link href="https://foo.zone/gemfeed/2026-02-14-til-meta-slash-commands-for-ai-workflows.html" />
- <id>https://foo.zone/gemfeed/2026-02-14-til-meta-slash-commands-for-ai-workflows.html</id>
- <updated>2026-02-14T14:00:00+02:00</updated>
- <author>
- <name>Paul Buetow aka snonux</name>
- <email>paul@dev.buetow.org</email>
- </author>
- <summary>Short post in 'This week I learned' style: what I tried, why it worked, and how you can replicate it. Format: Problem → Approach → Copy/paste → Result → Gotchas. Full reference for the meta-commands lives in a separate post.</summary>
- <content type="xhtml">
- <div xmlns="http://www.w3.org/1999/xhtml">
- <h1 style='display: inline' id='til-meta-slash-commands-for-reusable-ai-prompts-and-context'>TIL: Meta slash-commands for reusable AI prompts and context</h1><br />
-<br />
-<span class='quote'>Published at 2026-02-14T14:00:00+02:00</span><br />
-<br />
-<span>Short post in "This week I learned" style: what I tried, why it worked, and how you can replicate it. Format: Problem → Approach → Copy/paste → Result → Gotchas. Full reference for the meta-commands lives in a separate post.</span><br />
-<br />
-<a class='textlink' href='./2026-02-14-meta-slash-commands-for-prompts-and-context.html'>Full reference: Meta slash-commands to manage prompts and context for coding agents</a><br />
-<br />
-<h2 style='display: inline' id='til'>TIL</h2><br />
-<br />
-<h3 style='display: inline' id='problem'>Problem</h3><br />
-<br />
-<span>When I use a coding agent (Cursor Agent, Claude Code CLI, Ampcode, etc.), I repeat the same requests: "review this function," "explain this error," "add tests for this module," "format this as a blog post." Typing long prompts from scratch is tedious; ad-hoc prompts are easy to forget. I also keep pasting the same background (API conventions, project rules, infra notes) into every session so the agent doesn&#39;t guess blindly.</span><br />
-<br />
-<h3 style='display: inline' id='approach'>Approach</h3><br />
-<br />
-<span>Treat prompts and context as first-class artefacts: store them as markdown files (one per slash-command or per context) in a dotfiles repo. Use a small set of *meta* slash-commands so the agent itself creates, updates, and deletes these files from the conversation—no hand-editing. Two kinds of artefacts:</span><br />
-<br />
-<ul>
-<li>Commands — Reusable workflows (e.g. review-code, explain-error). Live in <span class='inlinecode'>commands/</span> as <span class='inlinecode'>.md</span> files.</li>
-<li>Context — Reusable background (API guidelines, runbooks, personas). Live in <span class='inlinecode'>context/</span> as <span class='inlinecode'>.md</span> files; you *load* one at session start so the agent has it in mind.</li>
-</ul><br />
-<h3 style='display: inline' id='copypaste'>Copy/paste</h3><br />
-<br />
-<span>Minimal workflow:</span><br />
-<br />
-<pre>
-/load-context api-guidelines
-</pre>
-<br />
-<span>Then ask the agent to implement a feature or fix a bug; it already has the guidelines.</span><br />
-<br />
-<span>To turn something you just did into a reusable command:</span><br />
-<br />
-<pre>
-/create-command review-code
-</pre>
-<br />
-<span>Full command reference (all meta-commands, parameters, examples):</span><br />
-<br />
-<a class='textlink' href='./2026-02-14-meta-slash-commands-for-prompts-and-context.html'>Meta slash-commands — full reference</a><br />
-<br />
-<h3 style='display: inline' id='result'>Result</h3><br />
-<br />
-<span>No more retyping long prompts; same prompt library across Cursor Agent, Claude Code CLI, OpenCode, Ampcode. Context is load-on-demand per session instead of pasting walls of text. Everything is versioned in git and synced across machines.</span><br />
-<br />
-<h3 style='display: inline' id='gotchas'>Gotchas</h3><br />
-<br />
-<ul>
-<li>Requires an agent that supports custom slash-commands (or reading prompt files from disk).</li>
-<li>Context is explicit: you must run <span class='inlinecode'>/load-context &lt;name&gt;</span> at session start; it&#39;s not implicit like some "skills" systems.</li>
-<li>A flat directory of commands can grow; if it gets unwieldy, consider grouping by skill or exposing via an MCP server later.</li>
-</ul><br />
-<h2 style='display: inline' id='beforeafter'>Before/After</h2><br />
-<br />
-<ul>
-<li>Before: Retyping or pasting long prompts each time; pasting API/project context into every session; prompts scattered and inconsistent across tools.</li>
-<li>After: <span class='inlinecode'>/load-context &lt;name&gt;</span> once per session; <span class='inlinecode'>/&lt;command&gt;</span> for repeatable tasks; commands and context in git, same library across agents.</li>
-</ul><br />
-<h2 style='display: inline' id='workflow-recipe'>Workflow recipe</h2><br />
-<br />
-<ul>
-<li>Steps: (1) Create or update context/commands via meta-commands when you have something worth reusing. (2) Start a session → run <span class='inlinecode'>/load-context &lt;name&gt;</span>. (3) Use slash-commands or ask ad-hoc; agent has background and consistent prompts.</li>
-<li>Tools: Cursor Agent (CLI), Claude Code CLI, OpenCode, Ampcode; markdown in e.g. <span class='inlinecode'>~/Notes/Prompts/commands/</span> and <span class='inlinecode'>~/Notes/Prompts/context/</span>.</li>
-<li>Impact: Saves retyping and keeps prompts consistent; one-time cost is creating the first context/command with the agent.</li>
-</ul><br />
-<h2 style='display: inline' id='micro-template-quick-reference'>Micro-template (quick reference)</h2><br />
-<br />
-<span>| Meta-command | Purpose | Good for |</span><br />
-<span>|--------------------|--------------------------------|-------------------------------------------|</span><br />
-<span>| /create-command | Create new slash-command | Turn current or recurring tasks into one |</span><br />
-<span>| /update-command | Edit existing slash-command | Refine after use |</span><br />
-<span>| /delete-command | Remove slash-command file | Clean up unused commands |</span><br />
-<span>| /create-context | Create new context file | Capture project/infra knowledge once |</span><br />
-<span>| /update-context | Edit existing context file | Keep context up to date |</span><br />
-<span>| /delete-context | Remove context file | Remove outdated context |</span><br />
-<span>| /load-context | Load context into conversation | Give agent background before tasks |</span><br />
-<br />
-<span>Example usage: <span class='inlinecode'>/load-context api-guidelines</span>, <span class='inlinecode'>/create-command review-code</span>, <span class='inlinecode'>/update-command review-code</span>. Full details and examples in the main post linked above.</span><br />
-<br />
-<span>Other related posts:</span><br />
-<br />
-<a class='textlink' href='./2026-02-14-meta-slash-commands-for-prompts-and-context.html'>2026-02-14 Meta slash-commands to manage prompts and context for coding agents</a><br />
-<a class='textlink' href='./2026-02-02-tmux-popup-editor-for-cursor-agent-prompts.html'>2026-02-02 A tmux popup editor for Cursor Agent CLI prompts</a><br />
-<br />
-<span>E-Mail your comments to <span class='inlinecode'>paul@nospam.buetow.org</span> :-)</span><br />
-<br />
-<a class='textlink' href='../'>Back to the main site</a><br />
- </div>
- </content>
- </entry>
- <entry>
<title>Meta slash-commands to manage prompts and context for coding agents</title>
<link href="https://foo.zone/gemfeed/2026-02-14-meta-slash-commands-for-prompts-and-context.html" />
<id>https://foo.zone/gemfeed/2026-02-14-meta-slash-commands-for-prompts-and-context.html</id>
@@ -591,7 +488,6 @@ mage test
<br />
<span>Other related posts:</span><br />
<br />
-<a class='textlink' href='./2026-02-14-til-meta-slash-commands-for-ai-workflows.html'>2026-02-14 TIL: Meta slash-commands for reusable AI prompts and context</a><br />
<a class='textlink' href='./2026-02-14-meta-slash-commands-for-prompts-and-context.html'>2026-02-14 Meta slash-commands to manage prompts and context for coding agents (You are currently reading this)</a><br />
<a class='textlink' href='./2026-02-02-tmux-popup-editor-for-cursor-agent-prompts.html'>2026-02-02 A tmux popup editor for Cursor Agent CLI prompts</a><br />
<br />
@@ -7711,7 +7607,6 @@ content = "{CODE}"
<br />
<span>Other related posts are:</span><br />
<br />
-<a class='textlink' href='./2026-02-14-til-meta-slash-commands-for-ai-workflows.html'>2026-02-14 TIL: Meta slash-commands for reusable AI prompts and context</a><br />
<a class='textlink' href='./2025-08-05-local-coding-llm-with-ollama.html'>2025-08-05 Local LLM for Coding with Ollama on macOS (You are currently reading this)</a><br />
<a class='textlink' href='./2025-06-22-task-samurai.html'>2025-06-22 Task Samurai: An agentic coding learning experiment</a><br />
<br />
@@ -10785,7 +10680,6 @@ http://www.gnu.org/software/src-highlite -->
<br />
<span>Other related posts are:</span><br />
<br />
-<a class='textlink' href='./2026-02-14-til-meta-slash-commands-for-ai-workflows.html'>2026-02-14 TIL: Meta slash-commands for reusable AI prompts and context</a><br />
<a class='textlink' href='./2025-08-05-local-coding-llm-with-ollama.html'>2025-08-05 Local LLM for Coding with Ollama on macOS</a><br />
<a class='textlink' href='./2025-06-22-task-samurai.html'>2025-06-22 Task Samurai: An agentic coding learning experiment (You are currently reading this)</a><br />
<br />
@@ -18418,4 +18312,356 @@ http://www.gnu.org/software/src-highlite -->
</div>
</content>
</entry>
+ <entry>
+ <title>KISS high-availability with OpenBSD</title>
+ <link href="https://foo.zone/gemfeed/2024-04-01-KISS-high-availability-with-OpenBSD.html" />
+ <id>https://foo.zone/gemfeed/2024-04-01-KISS-high-availability-with-OpenBSD.html</id>
+ <updated>2024-03-30T22:12:56+02:00</updated>
+ <author>
+ <name>Paul Buetow aka snonux</name>
+ <email>paul@dev.buetow.org</email>
+ </author>
+ <summary>I have always wanted a highly available setup for my personal websites. I could have used off-the-shelf hosting solutions or hosted my sites in an AWS S3 bucket. I have used technologies like (in unsorted and slightly unrelated order) BGP, LVS/IPVS, ldirectord, Pacemaker, STONITH, scripted VIP failover via ARP, heartbeat, heartbeat2, Corosync, keepalived, DRBD, and commercial F5 Load Balancers for high availability at work. </summary>
+ <content type="xhtml">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <h1 style='display: inline' id='kiss-high-availability-with-openbsd'>KISS high-availability with OpenBSD</h1><br />
+<br />
+<span class='quote'>Published at 2024-03-30T22:12:56+02:00</span><br />
+<br />
+<span>I have always wanted a highly available setup for my personal websites. I could have used off-the-shelf hosting solutions or hosted my sites in an AWS S3 bucket. I have used technologies like (in unsorted and slightly unrelated order) BGP, LVS/IPVS, ldirectord, Pacemaker, STONITH, scripted VIP failover via ARP, heartbeat, heartbeat2, Corosync, keepalived, DRBD, and commercial F5 Load Balancers for high availability at work. </span><br />
+<br />
+<span>But still, my personal sites were never highly available. All those technologies are great for professional use, but I was looking for something much more straightforward for my personal space - something as KISS (keep it simple and stupid) as possible.</span><br />
+<br />
+<span>It would be fine if my personal website wasn&#39;t highly available, but the geek in me wants it anyway.</span><br />
+<br />
+<span class='quote'>PS: ASCII-art below reflects an OpenBSD under-water world with all the tools available in the base system.</span><br />
+<br />
+<pre>
+Art by Michael J. Penick (mod. by Paul B.)
+ ACME-sky
+ __________
+ / nsd tower\ (
+ /____________\ (\) awk-ward
+ |:_:_:_:_:_| )) plant
+ |_:_,--.:_:| dig-bubble (\// )
+ |:_:|__|_:_| relayd-castle _ ) )) ((
+ _ |_ _ :_:| _ _ _ (_) (((( /)\`
+ | |_| |_| | _| | |_| |_| | o \\)) (( (
+ \_:_:_:_:/|_|_|_|\:_:_:_:_/ . (( ))))
+ |_,-._:_:_:_:_:_:_:_.-,_| )) ((//
+ |:|_|:_:_:,---,:_:_:|_|:| ,-. )/
+ |_:_:_:_,&#39;puffy `,_:_:_:_| _ o ,;&#39;))((
+ |:_:_:_/ _ | _ \_:_:_:| (_O (( ))
+_____|_:_:_| (o)-(o) |_:_:_|--&#39;`-. ,--. ksh under-water (((\&#39;/
+ &#39;, ;|:_:_:| -( .-. )- |:_:_:| &#39;, ; `--._\ /,---.~ goat \`))
+. ` |_:_:_| \`-&#39;/ |_:_:_|. ` . ` /()\.__( ) .,-----&#39;`-\(( sed-root
+ &#39;, ;|:_:_:| `-&#39; |:_:_:| &#39;, ; &#39;, ; `--&#39;| \ &#39;, ; &#39;, ; &#39;,&#39;)).,--
+. ` MJP ` . ` . ` . ` . httpd-soil ` . . ` . ` . ` . ` . `
+ &#39;, ; &#39;, ; &#39;, ; &#39;, ; &#39;, ; &#39;, ; &#39;, ; &#39;, ; &#39;, ; &#39;, ; &#39;, ; &#39;, ; &#39;, ; &#39;, ;
+
+</pre>
+<br />
+<h2 style='display: inline' id='table-of-contents'>Table of Contents</h2><br />
+<br />
+<ul>
+<li><a href='#kiss-high-availability-with-openbsd'>KISS high-availability with OpenBSD</a></li>
+<li>⇢ <a href='#my-auto-failover-requirements'>My auto-failover requirements</a></li>
+<li>⇢ <a href='#my-ha-solution'>My HA solution</a></li>
+<li>⇢ ⇢ <a href='#only-openbsd-base-installation-required'>Only OpenBSD base installation required</a></li>
+<li>⇢ ⇢ <a href='#fairly-cheap-and-geo-redundant'>Fairly cheap and geo-redundant</a></li>
+<li>⇢ ⇢ <a href='#failover-time-and-split-brain'>Failover time and split-brain</a></li>
+<li>⇢ ⇢ <a href='#failover-support-for-multiple-protocols'>Failover support for multiple protocols</a></li>
+<li>⇢ ⇢ <a href='#let-s-encrypt-tls-certificates'>Let&#39;s encrypt TLS certificates</a></li>
+<li>⇢ ⇢ <a href='#monitoring'>Monitoring</a></li>
+<li>⇢ ⇢ <a href='#rex-automation'>Rex automation</a></li>
+<li>⇢ <a href='#more-ha'>More HA</a></li>
+</ul><br />
+<h2 style='display: inline' id='my-auto-failover-requirements'>My auto-failover requirements</h2><br />
+<br />
+<ul>
+<li>Be OpenBSD-based (I prefer OpenBSD because of the cleanliness and good documentation) and rely on as few external packages as possible. </li>
+<li>Don&#39;t rely on the hottest and newest tech (don&#39;t want to migrate everything to a new and fancier technology next month already!).</li>
+<li>It should be reasonably cheap. I want to avoid paying a premium for floating IPs or fancy Elastic Load Balancers.</li>
+<li>It should be geo-redundant. </li>
+<li>It&#39;s fine if my sites aren&#39;t reachable for five or ten minutes every other month. Due to their static nature, I don&#39;t care if there&#39;s a split-brain scenario where some requests reach one server and other requests reach another server.</li>
+<li>Failover should work for both HTTP/HTTPS and Gemini protocols. My self-hosted MTAs and DNS servers should also be highly available.</li>
+<li>Let&#39;s Encrypt TLS certificates should always work (before and after a failover).</li>
+<li>Have good monitoring in place so I know when a failover was performed and when something went wrong with the failover.</li>
+<li>Don&#39;t configure everything manually. The configuration should be automated and reproducible.</li>
+</ul><br />
+<h2 style='display: inline' id='my-ha-solution'>My HA solution</h2><br />
+<br />
+<h3 style='display: inline' id='only-openbsd-base-installation-required'>Only OpenBSD base installation required</h3><br />
+<br />
+<span>My HA solution for Web and Gemini is based on DNS (OpenBSD&#39;s <span class='inlinecode'>nsd</span>) and a simple shell script (OpenBSD&#39;s <span class='inlinecode'>ksh</span> and some little <span class='inlinecode'>sed</span> and <span class='inlinecode'>awk</span> and <span class='inlinecode'>grep</span>). All software used here is part of the OpenBSD base system and no external package needs to be installed - OpenBSD is a complete operating system.</span><br />
+<br />
+<a class='textlink' href='https://man.OpenBSD.org/nsd.8'>https://man.OpenBSD.org/nsd.8</a><br />
+<a class='textlink' href='https://man.OpenBSD.org/ksh'>https://man.OpenBSD.org/ksh</a><br />
+<a class='textlink' href='https://man.OpenBSD.org/awk'>https://man.OpenBSD.org/awk</a><br />
+<a class='textlink' href='https://man.OpenBSD.org/sed'>https://man.OpenBSD.org/sed</a><br />
+<a class='textlink' href='https://man.OpenBSD.org/dig'>https://man.OpenBSD.org/dig</a><br />
+<a class='textlink' href='https://man.OpenBSD.org/ftp'>https://man.OpenBSD.org/ftp</a><br />
+<a class='textlink' href='https://man.OpenBSD.org/cron'>https://man.OpenBSD.org/cron</a><br />
+<br />
+<span>I also used the <span class='inlinecode'>dig</span> (for DNS checks) and <span class='inlinecode'>ftp</span> (for HTTP/HTTPS checks) programs. </span><br />
+<br />
+<span>The DNS failover is performed automatically between the two OpenBSD VMs involved (my setup doesn&#39;t require any quorum for a failover, so there isn&#39;t a need for a 3rd VM). The <span class='inlinecode'>ksh</span> script, executed once per minute via CRON (on both VMs), performs a health check to determine whether the current master node is available. If the current master isn&#39;t available (no HTTP response as expected), a failover is performed to the standby VM: </span><br />
+<br />
+<!-- Generator: GNU source-highlight 3.1.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><i><font color="silver">#!/bin/ksh</font></i>
+
+ZONES_DIR=/var/nsd/zones/master/
+DEFAULT_MASTER=fishfinger.buetow.org
+DEFAULT_STANDBY=blowfish.buetow.org
+
+determine_master_and_standby () {
+ <b><u><font color="#000000">local</font></u></b> master=$DEFAULT_MASTER
+ <b><u><font color="#000000">local</font></u></b> standby=$DEFAULT_STANDBY
+
+ .
+ .
+ .
+
+ <b><u><font color="#000000">local</font></u></b> -i health_ok=<font color="#000000">1</font>
+ <b><u><font color="#000000">if</font></u></b> ! ftp -<font color="#000000">4</font> -o - https://$master/index.txt | grep -q <font color="#808080">"Welcome to $master"</font>; <b><u><font color="#000000">then</font></u></b>
+ echo <font color="#808080">"https://$master/index.txt IPv4 health check failed"</font>
+ health_ok=<font color="#000000">0</font>
+ <b><u><font color="#000000">elif</font></u></b> ! ftp -<font color="#000000">6</font> -o - https://$master/index.txt | grep -q <font color="#808080">"Welcome to $master"</font>; <b><u><font color="#000000">then</font></u></b>
+ echo <font color="#808080">"https://$master/index.txt IPv6 health check failed"</font>
+ health_ok=<font color="#000000">0</font>
+ <b><u><font color="#000000">fi</font></u></b>
+ <b><u><font color="#000000">if</font></u></b> [ $health_ok -eq <font color="#000000">0</font> ]; <b><u><font color="#000000">then</font></u></b>
+ <b><u><font color="#000000">local</font></u></b> tmp=$master
+ master=$standby
+ standby=$tmp
+ <b><u><font color="#000000">fi</font></u></b>
+
+ .
+ .
+ .
+}
+</pre>
+<br />
+<span>The failover scripts looks for the <span class='inlinecode'> ; Enable failover</span> string in the DNS zone files and swaps the <span class='inlinecode'>A</span> and <span class='inlinecode'>AAAA</span> records of the DNS entries accordingly:</span><br />
+<br />
+<!-- Generator: GNU source-highlight 3.1.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre>fishfinger$ grep failover /var/nsd/zones/master/foo.zone.zone
+ <font color="#000000">300</font> IN A <font color="#000000">46.23</font>.<font color="#000000">94.99</font> ; Enable failover
+ <font color="#000000">300</font> IN AAAA 2a03:<font color="#000000">6000</font>:6f67:<font color="#000000">624</font>::<font color="#000000">99</font> ; Enable failover
+www <font color="#000000">300</font> IN A <font color="#000000">46.23</font>.<font color="#000000">94.99</font> ; Enable failover
+www <font color="#000000">300</font> IN AAAA 2a03:<font color="#000000">6000</font>:6f67:<font color="#000000">624</font>::<font color="#000000">99</font> ; Enable failover
+standby <font color="#000000">300</font> IN A <font color="#000000">23.88</font>.<font color="#000000">35.144</font> ; Enable failover
+standby <font color="#000000">300</font> IN AAAA 2a01:4f8:c17:20f1::<font color="#000000">42</font> ; Enable failover
+</pre>
+<br />
+<!-- Generator: GNU source-highlight 3.1.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre>transform () {
+ sed -E <font color="#808080">'</font>
+<font color="#808080"> /IN A .*; Enable failover/ {</font>
+<font color="#808080"> /^standby/! {</font>
+<font color="#808080"> s/^(.*) 300 IN A (.*) ; (.*)/</font>\1<font color="#808080"> 300 IN A '</font>$(cat /var/nsd/run/master_a)<font color="#808080">' ; </font>\3<font color="#808080">/;</font>
+<font color="#808080"> }</font>
+<font color="#808080"> /^standby/ {</font>
+<font color="#808080"> s/^(.*) 300 IN A (.*) ; (.*)/</font>\1<font color="#808080"> 300 IN A '</font>$(cat /var/nsd/run/standby_a)<font color="#808080">' ; </font>\3<font color="#808080">/;</font>
+<font color="#808080"> }</font>
+<font color="#808080"> }</font>
+<font color="#808080"> /IN AAAA .*; Enable failover/ {</font>
+<font color="#808080"> /^standby/! {</font>
+<font color="#808080"> s/^(.*) 300 IN AAAA (.*) ; (.*)/</font>\1<font color="#808080"> 300 IN AAAA '</font>$(cat /var/nsd/run/master_aaaa)<font color="#808080">' ; </font>\3<font color="#808080">/;</font>
+<font color="#808080"> }</font>
+<font color="#808080"> /^standby/ {</font>
+<font color="#808080"> s/^(.*) 300 IN AAAA (.*) ; (.*)/</font>\1<font color="#808080"> 300 IN AAAA '</font>$(cat /var/nsd/run/standby_aaaa)<font color="#808080">' ; </font>\3<font color="#808080">/;</font>
+<font color="#808080"> }</font>
+<font color="#808080"> }</font>
+<font color="#808080"> / ; serial/ {</font>
+<font color="#808080"> s/^( +) ([0-9]+) .*; (.*)/</font>\1<font color="#808080"> '</font>$(date +%s)<font color="#808080">' ; </font>\3<font color="#808080">/;</font>
+<font color="#808080"> }</font>
+<font color="#808080"> '</font>
+}
+</pre>
+<br />
+<span>After the failover, the script reloads <span class='inlinecode'>nsd</span> and performs a sanity check to see if DNS still works. If not, a rollback will be performed:</span><br />
+<br />
+<!-- Generator: GNU source-highlight 3.1.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><i><font color="silver">#! Race condition !#</font></i>
+
+<b><u><font color="#000000">if</font></u></b> [ -f $zone_file.bak ]; <b><u><font color="#000000">then</font></u></b>
+ mv $zone_file.bak $zone_file
+<b><u><font color="#000000">fi</font></u></b>
+
+cat $zone_file | transform &gt; $zone_file.new.tmp
+
+grep -v <font color="#808080">' ; serial'</font> $zone_file.new.tmp &gt; $zone_file.new.noserial.tmp
+grep -v <font color="#808080">' ; serial'</font> $zone_file &gt; $zone_file.old.noserial.tmp
+
+echo <font color="#808080">"Has zone $zone_file changed?"</font>
+<b><u><font color="#000000">if</font></u></b> diff -u $zone_file.old.noserial.tmp $zone_file.new.noserial.tmp; <b><u><font color="#000000">then</font></u></b>
+ echo <font color="#808080">"The zone $zone_file hasn't changed"</font>
+ rm $zone_file.*.tmp
+ <b><u><font color="#000000">return</font></u></b> <font color="#000000">0</font>
+<b><u><font color="#000000">fi</font></u></b>
+
+cp $zone_file $zone_file.bak
+mv $zone_file.new.tmp $zone_file
+rm $zone_file.*.tmp
+echo <font color="#808080">"Reloading nsd"</font>
+nsd-control reload
+
+<b><u><font color="#000000">if</font></u></b> ! zone_is_ok $zone; <b><u><font color="#000000">then</font></u></b>
+ echo <font color="#808080">"Rolling back $zone_file changes"</font>
+ cp $zone_file $zone_file.invalid
+ mv $zone_file.bak $zone_file
+ echo <font color="#808080">"Reloading nsd"</font>
+ nsd-control reload
+ zone_is_ok $zone
+ <b><u><font color="#000000">return</font></u></b> <font color="#000000">3</font>
+<b><u><font color="#000000">fi</font></u></b>
+
+<b><u><font color="#000000">for</font></u></b> cleanup <b><u><font color="#000000">in</font></u></b> invalid bak; <b><u><font color="#000000">do</font></u></b>
+ <b><u><font color="#000000">if</font></u></b> [ -f $zone_file.$cleanup ]; <b><u><font color="#000000">then</font></u></b>
+ rm $zone_file.$cleanup
+ <b><u><font color="#000000">fi</font></u></b>
+<b><u><font color="#000000">done</font></u></b>