diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-14 23:21:53 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-14 23:21:53 +0200 |
| commit | 372e68ea8517a82f1d15bb83cf84962b971e41ea (patch) | |
| tree | 9fa712a65e9d8b00b64654549bc67375bed95c40 /gemfeed | |
| parent | 8bc73bfdbd2b4f7a167baf3cd075dc07e89d6cd4 (diff) | |
Update content for html
Diffstat (limited to 'gemfeed')
| -rw-r--r-- | gemfeed/2025-06-22-task-samurai.html | 1 | ||||
| -rw-r--r-- | gemfeed/2025-08-05-local-coding-llm-with-ollama.html | 1 | ||||
| -rw-r--r-- | gemfeed/2026-02-14-meta-slash-commands-for-prompts-and-context.html | 1 | ||||
| -rw-r--r-- | gemfeed/2026-02-14-til-meta-slash-commands-for-ai-workflows.html | 110 | ||||
| -rw-r--r-- | gemfeed/atom.xml | 460 | ||||
| -rw-r--r-- | gemfeed/index.html | 1 |
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'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 <name></span> at session start; it'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 <name></span> once per session; <span class='inlinecode'>/<command></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 <name></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'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 <name></span> at session start; it'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 <name></span> once per session; <span class='inlinecode'>/<command></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 <name></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'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 \\)) (( ( + \_:_:_:_:/|_|_|_|\:_:_:_:_/ . (( )))) + |_,-._:_:_:_:_:_:_:_.-,_| )) ((// + |:|_|:_:_:,---,:_:_:|_|:| ,-. )/ + |_:_:_:_,'puffy `,_:_:_:_| _ o ,;'))(( + |:_:_:_/ _ | _ \_:_:_:| (_O (( )) +_____|_:_:_| (o)-(o) |_:_:_|--'`-. ,--. ksh under-water (((\'/ + ', ;|:_:_:| -( .-. )- |:_:_:| ', ; `--._\ /,---.~ goat \`)) +. ` |_:_:_| \`-'/ |_:_:_|. ` . ` /()\.__( ) .,-----'`-\(( sed-root + ', ;|:_:_:| `-' |:_:_:| ', ; ', ; `--'| \ ', ; ', ; ',')).,-- +. ` MJP ` . ` . ` . ` . httpd-soil ` . . ` . ` . ` . ` . ` + ', ; ', ; ', ; ', ; ', ; ', ; ', ; ', ; ', ; ', ; ', ; ', ; ', ; ', ; + +</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'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't rely on the hottest and newest tech (don'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's fine if my sites aren't reachable for five or ten minutes every other month. Due to their static nature, I don't care if there'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'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'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's <span class='inlinecode'>nsd</span>) and a simple shell script (OpenBSD'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't require any quorum for a failover, so there isn'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'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 > $zone_file.new.tmp + +grep -v <font color="#808080">' ; serial'</font> $zone_file.new.tmp > $zone_file.new.noserial.tmp +grep -v <font color="#808080">' ; serial'</font> $zone_file > $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> |
