<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[BrightBot.co.uk]]></title><description><![CDATA[Smart Home Technology and Home Lab adventures]]></description><link>https://brightbot.co.uk/</link><image><url>https://brightbot.co.uk/favicon.png</url><title>BrightBot.co.uk</title><link>https://brightbot.co.uk/</link></image><generator>Ghost 2.9</generator><lastBuildDate>Tue, 07 Oct 2025 10:41:13 GMT</lastBuildDate><atom:link href="https://brightbot.co.uk/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Verifying Your GitHub Commits with GPG or SSH]]></title><description><![CDATA[If you’ve been following along with the previous posts on managing multiple Git identities and multiple SSH keys, this next step adds a nice layer of polish — verified commits.

You’ve probably noticed some commits on GitHub have a little “Verified” badge next to them.
That badge tells others (and your future self) that GitHub has confirmed the commit really came from you — not someone impersonating your name and email.

Let’s walk through how to set up commit signing for your personal and work ]]></description><link>https://brightbot.co.uk/verifying-your-github-commits-with-gpg-or-ssh/</link><guid isPermaLink="false">Ghost__Post__68e4237264bc86d5eba401d5</guid><category><![CDATA[Infrastructure]]></category><category><![CDATA[Ansible]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Home Lab]]></category><dc:creator><![CDATA[Andrew Townsend]]></dc:creator><pubDate>Mon, 06 Oct 2025 20:28:52 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1728496120908-4fa5eb92ebee?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fHNpZ25lZHxlbnwwfHx8fDE3NTk3ODI1MTd8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1728496120908-4fa5eb92ebee?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fHNpZ25lZHxlbnwwfHx8fDE3NTk3ODI1MTd8MA&ixlib=rb-4.1.0&q=80&w=2000" alt="Verifying Your GitHub Commits with GPG or SSH"/><p>If you’ve been following along with the previous posts on managing <a href="https://ghost.atathome.me/managing-multiple-git-authors-by-directory/" rel="noreferrer">multiple Git identities</a> and <a href="https://ghost.atathome.me/managing-multiple-git-ssh-keys-by-directory/" rel="noreferrer">multiple SSH keys</a>, this next step adds a nice layer of polish — <strong>verified commits</strong>.</p><p>You’ve probably noticed some commits on GitHub have a little <strong>“Verified”</strong> badge next to them.<br>That badge tells others (and your future self) that GitHub has confirmed the commit really came from you — not someone impersonating your name and email.</br></p><p>Let’s walk through how to set up <strong>commit signing</strong> for your personal and work Git accounts.</p><hr><h2 id="%F0%9F%A7%A9-why-sign-commits">🧩 Why Sign Commits?</h2><p>Git already tracks who made each commit, but those details (name and email) are easy to fake — they’re just text in your config.</p><p>Signing your commits adds <strong>cryptographic proof</strong> that they actually came from you.</p><p>Benefits:</p><ul><li>✅ Verified commits on GitHub</li><li>🧠 Stronger identity assurance for open-source work</li><li>🔒 Prevents impersonation or accidental misattribution</li></ul><hr><h2 id="%F0%9F%94%90-two-ways-to-sign-commits">🔐 Two Ways to Sign Commits</h2><p>GitHub supports <strong>two signing methods</strong>:</p><ol><li><strong>GPG (GNU Privacy Guard)</strong> – traditional, widely supported</li><li><strong>SSH signing</strong> – newer and simpler (especially if you already use SSH keys)</li></ol><p>We’ll go through both, starting with SSH since it’s the easiest to set up.</p><hr><h2 id="%E2%9A%99%EF%B8%8F-option-1-%E2%80%93-signing-commits-with-ssh">⚙️ Option 1 – Signing Commits with SSH</h2><p>If you already have SSH keys for GitHub (from <a href="https://ghost.atathome.me/managing-multiple-git-ssh-keys-by-directory/" rel="noreferrer">Part 2</a>), you can use them to sign commits.</p><h3 id="1-check-your-git-version">1. Check your Git version</h3><p>SSH commit signing requires Git <strong>2.34+</strong>:</p><pre><code class="language-bash">git --version</code></pre><p>If it’s older, update it before continuing.</p><h3 id="2-tell-git-to-use-ssh-signing">2. Tell Git to use SSH signing</h3><p>Pick which key you want to use (personal or work) and set it as the signing key:</p><pre><code class="language-bash">git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519_personal.pub
git config --global commit.gpgsign true
</code></pre><p>If you use separate identities, you can set the signing key per directory using the same <code>includeIf</code> setup from earlier:</p><pre><code class="language-bash">[includeIf "gitdir:~/work/"]
    path = ~/.gitconfig-work</code></pre><p>And inside <code>~/.gitconfig-work</code>:</p><pre><code class="language-bash">[gpg]
    format = ssh
[user]
    signingkey = ~/.ssh/id_ed25519_work.pub</code></pre><h3 id="3-add-the-public-key-to-github">3. Add the public key to GitHub</h3><p>Copy your SSH public key:</p><pre><code class="language-bash">cat ~/.ssh/id_ed25519_personal.pub</code></pre><p>Go to <strong>GitHub → Settings → SSH and GPG keys → New SSH key</strong>,<br>then choose <strong>"Signing Key"</strong> as the type.</br></p><p>Once saved, any commit signed with that SSH key will show up as <strong>Verified</strong> on GitHub.</p><hr><h2 id="%F0%9F%94%91-option-2-%E2%80%93-signing-commits-with-gpg">🔑 Option 2 – Signing Commits with GPG</h2><p>If you prefer GPG, or already use it for encryption, you can generate and add a GPG key instead.</p><h3 id="1-generate-a-gpg-key">1. Generate a GPG key</h3><pre><code class="language-bash">gpg --full-generate-key</code></pre><p>Choose:</p><ul><li>Key type: <code>RSA and RSA</code></li><li>Key size: <code>4096</code></li><li>Expiration: up to you</li><li>Real name &amp; email: must match your Git config</li></ul><p>Then list your keys:</p><pre><code class="language-bash">gpg --list-secret-keys --keyid-format=long
</code></pre><h3 id="2-set-your-signing-key-in-git">2. Set your signing key in Git</h3><p>Take the long key ID (the part after <code>sec rsa4096/</code>) and configure Git:</p><pre><code class="language-bash">git config --global user.signingkey YOURKEYID
git config --global commit.gpgsign true</code></pre><h3 id="3-add-your-public-key-to-github">3. Add your public key to GitHub</h3><p>Export your public key:</p><pre><code class="language-bash">gpg --armor --export YOURKEYID
</code></pre><p>Copy the output and add it at<br><strong>GitHub → Settings → SSH and GPG keys → New GPG key</strong></br></p><p>That’s it — your GPG-signed commits will now show as verified too.</p><hr><h2 id="%F0%9F%A7%A0-testing-it">🧠 Testing It</h2><p>Make a test commit:</p><pre><code class="language-bash">git commit -S -m "test signed commit"
</code></pre><p>Push it to GitHub, then open the commit in your repo — you should see:</p><blockquote>✅ Verified</blockquote><p>For example, when enabled on GitHub it should look like this, see previous <code>Unverified</code> and then subsequent <code>Verified</code> commit.</p><figure class="kg-card kg-image-card"><img src="https://content.brightbot.co.uk/2025/10/Screenshot-2025-10-06-at-21.27.29.png" class="kg-image" alt="Verifying Your GitHub Commits with GPG or SSH" loading="lazy" width="3294" height="568"/></figure><p>If you don’t, check that:</p><ul><li>The key email matches your GitHub account email</li><li>The right key was added as a signing key</li><li>You’re not committing as a different user/SSH alias</li></ul><hr><h2 id="%F0%9F%A7%A9-bonus-tip-%E2%80%93-visual-verification">🧩 Bonus Tip – Visual Verification</h2><p>You can confirm locally that commits are signed and valid:</p><pre><code class="language-bash">git log --show-signature
</code></pre><p>This will show which key signed each commit.</p><hr><h2 id="%E2%9C%85-summary">✅ Summary</h2><p>You’ve now got:</p><ul><li><a href="https://ghost.atathome.me/managing-multiple-git-authors-by-directory/" rel="noreferrer">Part 1</a>: 🔄 Automatic author switching per directory</li><li><a href="https://ghost.atathome.me/managing-multiple-git-ssh-keys-by-directory/" rel="noreferrer">Part 2</a>: 🔑 SSH key separation for multiple Git accounts</li><li>Part 3 (This post): 🪪 Verified commits on GitHub</li></ul><p>Together, these make your Git setup more professional, secure, and organised — whether you’re working across open-source, side projects, or enterprise repos.</p></hr></hr></hr></hr></hr></hr></hr>]]></content:encoded></item><item><title><![CDATA[Managing Multiple Git SSH Keys by Directory]]></title><description><![CDATA[In the last post, we configured Git to automatically use different user names and emails depending on the project directory.
That solved one half of the problem.

But if you use multiple Git accounts — say, one for work, one for personal projects — you’ll also need to make sure Git connects using the right SSH key for each.

Let’s fix that next.


The Problem

By default, Git uses your system-wide SSH key (typically ~/.ssh/id_rsa or ~/.ssh/id_ed25519).
If both your personal and work GitHub accou]]></description><link>https://brightbot.co.uk/managing-multiple-git-ssh-keys-by-directory/</link><guid isPermaLink="false">Ghost__Post__68e41c4864bc86d5eba401af</guid><category><![CDATA[Infrastructure]]></category><category><![CDATA[Ansible]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Home Lab]]></category><dc:creator><![CDATA[Andrew Townsend]]></dc:creator><pubDate>Mon, 06 Oct 2025 20:07:48 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1643804926339-e94f0a655185?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fGtleXN8ZW58MHx8fHwxNzU5NzgwNTk3fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1643804926339-e94f0a655185?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fGtleXN8ZW58MHx8fHwxNzU5NzgwNTk3fDA&ixlib=rb-4.1.0&q=80&w=2000" alt="Managing Multiple Git SSH Keys by Directory"/><p><a href="https://ghost.atathome.me/managing-multiple-git-authors-by-directory/" rel="noreferrer">In the last post</a>, we configured Git to automatically use different <strong>user names and emails</strong> depending on the project directory.<br>That solved one half of the problem.</br></p><p>But if you use <strong>multiple Git accounts</strong> — say, one for work, one for personal projects — you’ll also need to make sure Git connects using the <strong>right SSH key</strong> for each.</p><p>Let’s fix that next.</p><hr><h2 id="the-problem">The Problem</h2><p>By default, Git uses your system-wide SSH key (typically <code>~/.ssh/id_rsa</code> or <code>~/.ssh/id_ed25519</code>).<br>If both your personal and work GitHub accounts use SSH, you can easily run into:</br></p><pre><code class="language-bash">ERROR: Permission denied (publickey)</code></pre><p>or worse — commits showing up under the wrong GitHub account.</p><p>The trick is to have <strong>separate SSH keys</strong> and tell your SSH client when to use which one.</p><hr><h2 id="%F0%9F%A7%B0-step-1-%E2%80%93-generate-your-keys">🧰 Step 1 – Generate Your Keys</h2><p>If you don’t already have separate keys, create them now.</p><pre><code class="language-bash"># Personal key
ssh-keygen -t ed25519 -f ~/.ssh/id_rsa_personal -C "personal@example.com"

# Work key
ssh-keygen -t ed25519 -f ~/.ssh/id_rsa_work -C "work@example.com"</code></pre><p>You’ll end up with two key pairs:</p><ul><li><code>id_ed25519_personal</code> / <code>id_ed25519_personal.pub</code></li><li><code>id_ed25519_work</code> / <code>id_ed25519_work.pub</code></li></ul><hr><h2 id="%F0%9F%A7%A9-step-2-%E2%80%93-add-keys-to-your-git-hosts">🧩 Step 2 – Add Keys to Your Git Hosts</h2><p>Add each <strong>public key</strong> (<code>.pub</code> file) to the correct account:</p><ul><li><strong>Personal GitHub</strong> → <em>Settings → SSH and GPG keys → New SSH key</em></li><li><strong>Work GitHub / GitLab</strong> → <em>Same path, different account</em></li></ul><p>Give them clear names like “Laptop Personal” and “Laptop Work.”</p><hr><h2 id="%E2%9A%99%EF%B8%8F-step-3-%E2%80%93-create-a-custom-ssh-config">⚙️ Step 3 – Create a Custom SSH Config</h2><p>Open (or create) <code>~/.ssh/config</code> and define custom host aliases for each account:</p><pre><code class="language-bash"># Personal GitHub
Host github-personal
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_rsa_personal
    IdentitiesOnly yes

# Work GitHub
Host github-work
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_rsa_work
    IdentitiesOnly yes

# Another Work Bitbucket
Host bitbucket
    HostName bitbucket.org
    User git
    IdentityFile ~/.ssh/id_rsa_work_bitbucket
    IdentitiesOnly yes
</code></pre><p>Now, instead of using <code>git@github.com</code>, you’ll use either:</p><ul><li><code>git@github-personal</code></li><li><code>git@github-work</code></li></ul><p>Each alias tells SSH which key to use. This will work for any provider, not just GitHub such as GitLab or Bitbucket etc.</p><hr><h2 id="%F0%9F%A7%B1-step-4-%E2%80%93-clone-repositories-with-the-right-alias">🧱 Step 4 – Clone Repositories with the Right Alias</h2><p>When cloning, use the correct alias:</p><pre><code class="language-bash"># Personal project
git clone git@github-personal:username/personal-repo.git

# Work project
git clone git@github-work:company/work-repo.git</code></pre><p>Git doesn’t care about the alias — it’s just an SSH shortcut that ensures the correct key gets used.</p><p>If you’ve already cloned a repo, you can edit the <code>.git/config</code> file inside it:</p><pre><code class="language-bash">[remote "origin"]
    url = git@github-work:company/work-repo.git
    fetch = +refs/heads/*:refs/remotes/origin/*</code></pre><hr><h2 id="%F0%9F%A7%A0-step-5-%E2%80%93-combine-with-conditional-git-configs">🧠 Step 5 – Combine with Conditional Git Configs</h2><p>If you followed the previous post, you can now combine the two setups:</p><ul><li>Use <code>includeIf</code> in <code>~/.gitconfig</code> to pick the <strong>right author details</strong>.</li><li>Use SSH aliases in <code>~/.ssh/config</code> to pick the <strong>right authentication key</strong>.</li></ul><p>That means everything just works when you’re in the right directory — no more switching accounts manually.</p><hr><h2 id="%F0%9F%A7%A9-example-directory-setup">🧩 Example Directory Setup</h2><pre><code class="language-bash">~/projects/personal/     → uses github-personal + personal identity  
~/work/                  → uses github-work + work identity  </code></pre><p>Each repo inside those folders automatically gets the right author <em>and</em> the right SSH key.</p><hr><h2 id="%E2%9C%85-summary">✅ Summary</h2><p>With a few lines of config, you’ve now built a clean, automatic Git environment that handles multiple accounts seamlessly.</p><p><strong>Benefits:</strong></p><ul><li>Separate identities and SSH keys per project type</li><li>No more “wrong account” commits or permission errors</li><li>Clean, organized Git and SSH setups that just work</li></ul><p>This approach is especially useful if you:</p><ul><li>Contribute to open-source projects with your personal account</li><li>Work for multiple clients</li><li>Keep your personal and professional Git presence strictly separated</li></ul><p>In Part 3 we go a step further and <a href="https://ghost.atathome.me/verifying-your-github-commits-with-gpg-or-ssh/" rel="noreferrer">verifying our commits providing confidence they are from trusted authors</a></p></hr></hr></hr></hr></hr></hr></hr></hr>]]></content:encoded></item><item><title><![CDATA[Managing Multiple Git Authors by Directory]]></title><description><![CDATA[If you contribute to both personal and work repositories, you’ve probably run into this:

You commit to a personal project — and realise your work email is all over it.

Or worse, you commit to a company repo using your personal identity.

Git only tracks one author configuration per system by default, but with a little setup, you can make Git automatically choose the right name and email based on the directory you’re working in.

Let’s walk through how to do it cleanly.


🧩 The Problem

By def]]></description><link>https://brightbot.co.uk/managing-multiple-git-authors-by-directory/</link><guid isPermaLink="false">Ghost__Post__68e41a4764bc86d5eba40175</guid><category><![CDATA[Infrastructure]]></category><category><![CDATA[Ansible]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Home Lab]]></category><dc:creator><![CDATA[Andrew Townsend]]></dc:creator><pubDate>Mon, 06 Oct 2025 19:44:53 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1555099962-4199c345e5dd?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDN8fHNvdXJjZSUyMGNvZGV8ZW58MHx8fHwxNzU5Nzc5ODQ3fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1555099962-4199c345e5dd?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMTc3M3wwfDF8c2VhcmNofDN8fHNvdXJjZSUyMGNvZGV8ZW58MHx8fHwxNzU5Nzc5ODQ3fDA&ixlib=rb-4.1.0&q=80&w=2000" alt="Managing Multiple Git Authors by Directory"/><p>If you contribute to both personal and work repositories, you’ve probably run into this:</p><p>You commit to a personal project — and realise your <strong>work email</strong> is all over it.</p><p>Or worse, you commit to a company repo using your personal identity.</p><p>Git only tracks one author configuration per system by default, but with a little setup, you can make Git automatically choose the right name and email <strong>based on the directory you’re working in</strong>.</p><p>Let’s walk through how to do it cleanly.</p><hr><h2 id="%F0%9F%A7%A9-the-problem">🧩 The Problem</h2><p>By default, Git uses the global configuration you set up when you first installed it:</p><pre><code class="language-bash">git config --global user.name "John Smith"
git config --global user.email "john@work.com"</code></pre><p>That means <strong>every commit</strong> — no matter which project — will use that identity.</p><p>If you use Git for both work and personal projects, this gets messy fast.</p><hr><h2 id="%F0%9F%9B%A0%EF%B8%8F-the-solution-per-directory-config">🛠️ The Solution: Per-Directory Config</h2><p>Git allows <strong>conditional includes</strong>, meaning you can load different configuration files depending on the path of your repository.</p><p>You can define separate config files for each identity and tell Git which one to use automatically based on directory.</p><hr><h2 id="%E2%9A%99%EF%B8%8F-step-1-%E2%80%93-set-your-global-config">⚙️ Step 1 – Set Your Global Config</h2><p>Keep your global Git config simple — it acts as the default.</p><pre><code class="language-bash">git config --global user.name "Personal Name"
git config --global user.email "personal@example.com"
</code></pre><p>Then open your global config file for editing:</p><pre><code class="language-bash">nano ~/.gitconfig
</code></pre><p>You’ll see something like:</p><pre><code class="language-bash">[user]
    name = Personal Name
    email = personal@example.com</code></pre><hr><h2 id="%E2%9A%99%EF%B8%8F-step-2-%E2%80%93-add-conditional-includes">⚙️ Step 2 – Add Conditional Includes</h2><p>Now, let’s add rules so Git automatically uses your <strong>work identity</strong> in a specific folder, and your <strong>personal identity</strong> elsewhere.</p><p>Append this to your <code>~/.gitconfig</code>:</p><pre><code class="language-bash">[includeIf "gitdir:~/work/"]
    path = ~/.gitconfig-work

[includeIf "gitdir:~/projects/personal/"]
    path = ~/.gitconfig-personal
</code></pre><p>Each <code>includeIf</code> rule checks the path of the repository.<br>If the repo lives inside <code>~/work/</code>, Git will include your <code>~/.gitconfig-work</code> settings.</br></p><blockquote>💡 The path <strong>must end with a slash (<code>/</code>)</strong> to match everything inside that directory.</blockquote><hr><h2 id="step-3-%E2%80%93-create-the-additional-config-files">Step 3 – Create the Additional Config Files</h2><p>Now create the two files:</p><p><strong><code>~/.gitconfig-work</code></strong></p><pre><code class="language-bash">[user]
    name = Work Name
    email = work@example.com</code></pre><p><strong><code>~/.gitconfig-personal</code></strong></p><pre><code class="language-bash">[user]
    name = Personal Name
    email = personal@example.com</code></pre><p>You can add as many of these as you need — for freelance clients, open-source accounts, or separate organisations.</p><hr><h2 id="%F0%9F%A7%AA-step-4-%E2%80%93-test-it">🧪 Step 4 – Test It</h2><p>Navigate into one of your repos and check which author Git is using:</p><pre><code class="language-bash">cd ~/work/project1

git config user.name
-&gt; Work Name

git config user.email
-&gt; work@example.com

cd ~/projects/personal/myproject2

git config user.name
-&gt; Personal Name

git config user.email
-&gt; personal@example.com</code></pre><p>You should see the correct identity based on the folder you’re in.</p><hr><h2 id="%F0%9F%A7%B0-bonus-tip-%E2%80%93-verify-before-you-commit">🧰 Bonus Tip – Verify Before You Commit</h2><p>If you want a quick reminder before committing, you can add an alias to show the current identity:</p><pre><code class="language-bash">git config --global alias.whoami '!git config user.name &amp;&amp; git config user.email'</code></pre><p>Now run:</p><pre><code class="language-bash">cd ~/work/anotherproject

git whoami
Work Name</code></pre><p>to double-check which author Git will use in the current repo.</p><hr><h2 id="%F0%9F%9B%A0%EF%B8%8F-fixing-any-previous-commits">🛠️ Fixing any previous commits</h2><p/><p>If you have accidentally committed with the wrong git author, you can always go back and alter the commit history, however note this can be a destructive process so use with caution! Where GIT_HASH is the commit just before the bad commit author you can substitute that and run the following:</p><pre><code class="language-bash">git rebase -r GIT_HASH \
    --exec 'git commit --amend --no-edit --reset-author'</code></pre><p>This will rebase ALL commits from that point resetting them against the currently configured author. </p><p>⚠️ <strong>Warning:</strong> This rewrites history — only do this on branches you control (not shared ones).</p><hr><h2 id="%E2%9C%85-summary">✅ Summary</h2><p>With conditional includes, Git becomes smart enough to switch identities automatically based on directory.</p><p><strong>Benefits:</strong></p><ul><li>No need to constantly reconfigure or remember which profile you’re using.</li><li>Prevents accidentally committing to personal projects with your work email.</li><li>Keeps your global configuration clean and simple.</li></ul><p>It’s a small setup that saves a lot of cleanup later — especially if you juggle multiple Git identities daily.</p><p>In Part 2 we go into the next step of using <a href="https://ghost.atathome.me/managing-multiple-git-ssh-keys-by-directory/" rel="noreferrer">separate SSH keys for multiple Git Accounts</a></p></hr></hr></hr></hr></hr></hr></hr></hr></hr>]]></content:encoded></item><item><title><![CDATA[Building a Laser Trip Sensor with a Photoelectric Sensor and ESPHome]]></title><description><![CDATA[Presence detection is a cornerstone of home automation. Whether you want lights that automatically switch on when someone walks into a room, or smarter security alerts, presence sensors give your automations real-world context.

While PIR (motion) sensors are common, another interesting option is the photoelectric sensor. These sensors detect when an object breaks a light beam, making them great for detecting someone passing through a doorway, entering a hallway, or triggering an event when some]]></description><link>https://brightbot.co.uk/building-a-laser-trip-sensor-with-a-photoelectric-sensor-and-esphome/</link><guid isPermaLink="false">Ghost__Post__68ddababcb5315e3604fce97</guid><category><![CDATA[Home Automation]]></category><category><![CDATA[ESPHome]]></category><dc:creator><![CDATA[Andrew Townsend]]></dc:creator><pubDate>Sun, 05 Oct 2025 12:09:25 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1574232889174-1a6051231cf6?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDR8fGxhemVyfGVufDB8fHx8MTc1OTY2NjE1MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1574232889174-1a6051231cf6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMTc3M3wwfDF8c2VhcmNofDR8fGxhemVyfGVufDB8fHx8MTc1OTY2NjE1MHww&ixlib=rb-4.1.0&q=80&w=2000" alt="Building a Laser Trip Sensor with a Photoelectric Sensor and ESPHome"/><p>Presence detection is a cornerstone of home automation. Whether you want lights that automatically switch on when someone walks into a room, or smarter security alerts, presence sensors give your automations real-world context.</p><p>While PIR (motion) sensors are common, another interesting option is the <strong>photoelectric sensor</strong>. These sensors detect when an object breaks a light beam, making them great for detecting someone passing through a doorway, entering a hallway, or triggering an event when something moves past.</p><p>In this post, I’ll walk through building a simple <strong>ESPHome-based presence sensor</strong> using a photoelectric sensor, an ESP microcontroller, and Home Assistant integration.</p><hr><h2 id="why-photoelectric-sensors">Why Photoelectric Sensors?</h2><p>Most people default to PIR sensors, but photoelectric sensors offer some nice benefits:</p><ul><li><strong>Directional detection</strong> – can be placed across a doorway or passage.</li><li><strong>More reliable indoors</strong> – less prone to false triggers from heat changes.</li><li><strong>Fast response</strong> – detects instantly when the beam is broken.</li><li><strong>Versatile</strong> – can detect objects, not just people.</li></ul><p>This makes them especially useful in <strong>hallways, staircases, garages, or even for counting entries/exits</strong>.</p><hr><h2 id="hardware-needed">Hardware Needed</h2><ul><li><strong>ESP8266 or ESP32</strong> board (NodeMCU, ESP32-DevKit, etc.).</li><li><strong>Photoelectric sensor</strong> (common 5V or 12V models, like E18-D80NK).</li><li><strong>Relay or MOSFET (optional)</strong> if the sensor requires different power than the ESP board.</li><li><strong>Wiring &amp; enclosure</strong> depending on where it’s installed.</li></ul><p>Most photoelectric sensors provide a digital output (HIGH/LOW) when the beam is broken, which makes them easy to integrate with ESPHome.</p><hr><h2 id="wiring-overview">Wiring Overview</h2><ul><li><strong>VCC</strong> of the sensor → 5V (or 12V depending on model).</li><li><strong>GND</strong> → Common ground with ESP board.</li><li><strong>OUT</strong> → Connect to an ESP GPIO pin (with level shifting if needed).</li></ul><hr><h2 id="esphome-configuration">ESPHome Configuration</h2><p>Here’s a simple YAML config to turn the sensor into a binary sensor in Home Assistant:</p><pre><code class="language-yaml">esphome:
  name: photo-sensor
  platform: ESP8266
  board: nodemcuv2

wifi:
  ssid: "YOUR_WIFI"
  password: "YOUR_PASSWORD"

api:
logger:
ota:

binary_sensor:
  - platform: gpio
    pin: D5
    name: "Presence Sensor"
    device_class: motion
    filters:
      - delayed_off: 500ms
</code></pre><p>✅ This setup creates a binary sensor entity in Home Assistant called <strong>Presence Sensor</strong>.<br>✅ <code>delayed_off</code> prevents flickering if the beam is quickly interrupted multiple times.</br></p><hr><h2 id="example-automations">Example Automations</h2><p>Once in Home Assistant, you can start building automations like:</p><ul><li><strong>Hallway lights</strong>: Turn on when someone walks through.</li><li><strong>Garage alert</strong>: Send a notification when the beam is broken after hours.</li><li><strong>Entry counting</strong>: Use two sensors side-by-side to count in/out movement.</li></ul><p>Example automation for lights:</p><pre><code class="language-yaml">alias: Hallway Lights On
trigger:
  - platform: state
    entity_id: binary_sensor.presence_sensor
    to: "on"
action:
  - service: light.turn_on
    target:
      entity_id: light.hallway
</code></pre><hr><h2 id="benefits-of-a-photoelectric-presence-sensor">Benefits of a Photoelectric Presence Sensor</h2><ul><li><strong>Accurate</strong> – triggers only when the beam is broken.</li><li><strong>Flexible</strong> – works for both people and objects.</li><li><strong>DIY-friendly</strong> – simple binary sensor integration with ESPHome.</li><li><strong>Expandable</strong> – add multiple sensors to track direction or build counters.</li></ul><hr><h2 id="conclusion">Conclusion</h2><p>A <strong>photoelectric sensor</strong> paired with <strong>ESPHome</strong> gives you a fast, reliable way to detect presence and movement in your smart home. It’s a great alternative to PIR sensors, especially in narrow or controlled spaces like doorways, staircases, or garages.</p><p>Once integrated with Home Assistant, the possibilities are endless — from simple light automation to more advanced occupancy tracking.</p></hr></hr></hr></hr></hr></hr></hr>]]></content:encoded></item><item><title><![CDATA[Building a 4-Zone Irrigation Controller with ESPHome - Part 1]]></title><description><![CDATA[Automating irrigation is one of those home automation projects that’s both practical and fun.

Instead of manually turning sprinklers or valves on and off, you can control watering schedules through your smart home — or even trigger watering based on weather or soil moisture sensors.

In this series, I’ll walk through building a 4-zone irrigation controller using ESPHome, some off-the-shelf components, and Home Assistant integration.


Series Overview

As I publish parts of this series I will up]]></description><link>https://brightbot.co.uk/building-a-4-zone-irrigation-controller-with-esphome-part-1/</link><guid isPermaLink="false">Ghost__Post__68dda75ccb5315e3604fce86</guid><category><![CDATA[Home Automation]]></category><category><![CDATA[ESPHome]]></category><dc:creator><![CDATA[Andrew Townsend]]></dc:creator><pubDate>Sat, 04 Oct 2025 20:44:38 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1495647688236-ed6ef40cb28b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDl8fGlycmlnYXRpb258ZW58MHx8fHwxNzU5NjA5NDIzfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1495647688236-ed6ef40cb28b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMTc3M3wwfDF8c2VhcmNofDl8fGlycmlnYXRpb258ZW58MHx8fHwxNzU5NjA5NDIzfDA&ixlib=rb-4.1.0&q=80&w=2000" alt="Building a 4-Zone Irrigation Controller with ESPHome - Part 1"/><p>Automating irrigation is one of those home automation projects that’s both <strong>practical</strong> and <strong>fun</strong>. </p><p>Instead of manually turning sprinklers or valves on and off, you can control watering schedules through your smart home — or even trigger watering based on weather or soil moisture sensors.</p><p>In this series, I’ll walk through building a <strong>4-zone irrigation controller</strong> using <strong>ESPHome</strong>, some off-the-shelf components, and Home Assistant integration.</p><hr><h2 id="series-overview">Series Overview</h2><p>As I publish parts of this series I will update the links between each part for easier navigation.</p><p><strong>Part 1 – Planning the Build &amp; Hardware Setup</strong><br>→ Components, software, wiring concept, safety.</br></p><p><strong>Part 2 – Prototype, Flashing ESPHome &amp; Writing the YAML</strong><br>→ ESPHome config, relay control, and testing each zone.</br></p><p><strong>Part 3 – Creating a custom PCB</strong><br>→ Walking through PCB design and ordering process</br></p><p><strong>Part 4 – Integrating with Home Assistant</strong><br>→ Adding all entities, automations, scheduling, and dashboard.</br></p><p><strong>Part 5 – Adding Sensors &amp; Smart Features</strong><br>→ Soil moisture, rain delay, weather data, and refinement.</br></p><p><strong>Part 6 – Final thoughts and future improvements</strong><br>→ Closing thoughts what I like, what I dislike and would do better next time.</br></p><hr><h3 id="why-build-a-custom-controller">Why Build a Custom Controller?</h3><p>Off-the-shelf irrigation systems work fine for simple setups — but they’re often limited, closed, and inflexible. By building your own controller with <strong>ESPHome</strong> and <strong>Home Assistant</strong>, you get:</p><ul><li><strong>Full control and customisation</strong> – tailor schedules, sensor logic, and automations exactly how you want.</li><li><strong>Local operation</strong> – no reliance on vendor cloud services or app subscriptions.</li><li><strong>Easy integration</strong> – connect with weather data, soil sensors, and other smart home devices.</li><li><strong>Expandability</strong> – add more zones, relays, or features later without replacing the hardware.</li><li><strong>Lower cost</strong> – use inexpensive components you can maintain or upgrade yourself.</li></ul><p>In short, a DIY controller gives you flexibility and transparency that commercial units rarely offer — and it’s far more satisfying to build something that perfectly fits <em>your</em> garden and your system.</p><hr><h2 id="why-esphome">Why ESPHome?</h2><p><strong>ESPHome</strong> is a framework for programming ESP8266/ESP32 microcontrollers with simple YAML configuration. Instead of writing C++ code, you declare sensors, switches, and automations in YAML, then flash them to your device. Benefits include:</p><ul><li><strong>Tight Home Assistant integration</strong> (auto discovery).</li><li><strong>Easy configuration and updates</strong> via Wi-Fi.</li><li><strong>Support for a huge range of sensors and relays.</strong></li><li><strong>Community-friendly</strong> with lots of real-world examples.</li></ul><p>Perfect for a project like irrigation control.</p><hr><h2 id="hardware-needed">Hardware Needed</h2><p>For a 4-zone controller, you’ll need at a minimum the following:</p><h3 id="electrical-components">Electrical Components</h3>
<!--kg-card-begin: html-->
<table>
<thead>
<tr>
<th>Component</th>
<th>Example</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Microcontroller</strong></td>
<td>ESP8266 (NodeMCU) or ESP32</td>
<td>Brain of the system</td>
</tr>
<tr>
<td><strong>Relay module (4-channel)</strong></td>
<td>5 V or 12 V logic</td>
<td>Controls solenoid valves</td>
</tr>
<tr>
<td><strong>Solenoid valves</strong></td>
<td>12 VAC garden valves</td>
<td>Open/close water flow</td>
</tr>
<tr>
<td><strong>Power supply</strong></td>
<td>12 VAC transformer (for valves) + 5 V DC (for ESP)</td>
<td>Powers everything</td>
</tr>
<tr>
<td><strong>Jumper wires &amp; terminals</strong></td>
<td>Dupont / screw connectors</td>
<td>Clean wiring</td>
</tr>
<tr>
<td><strong>Enclosure</strong></td>
<td>Weather-resistant box</td>
<td>Keeps electronics dry</td>
</tr>
<tr>
<td><em>(optional)</em> <strong>Manual override switch or button</strong></td>
<td>—</td>
<td>Run a zone manually</td>
</tr>
</tbody>
</table>
<!--kg-card-end: html-->
<h3 id="waterhose-components">Water/Hose Components</h3>
<!--kg-card-begin: html-->
<table class="tg"><thead>
  <tr>
    <th class="tg-0lax">Component</th>
    <th class="tg-0lax">Example</th>
    <th class="tg-0lax">Purpose</th>
  </tr></thead>
<tbody>
  <tr>
    <td class="tg-0lax">Hose Manifold</td>
    <td class="tg-0lax"/>
    <td class="tg-0lax">Used to split the cold water supply to each zone</td>
  </tr>
  <tr>
    <td class="tg-0lax">Hose to G-thread adapters</td>
    <td class="tg-0lax">G3/4 to Hose</td>
    <td class="tg-0lax">Used to connect various hose pipes to solenoid valves, depending on the valve depends on which connectors/adapters are required.</td>
  </tr>
  <tr>
    <td class="tg-0lax">G-thread to irrigation adapters</td>
    <td class="tg-0lax"/>
    <td class="tg-0lax">Dependant on the irrigation system you use, you may need a specific adapter to go from the solenoid to pipe</td>
  </tr>
  <tr>
    <td class="tg-0lax">Irrigation kit/drip feeders</td>
    <td class="tg-0lax">Claber Dripper Irrigation Set for Ten Plants</td>
    <td class="tg-0lax">Provides the irrigation part of the solution with suitable pipe and drip feeders, this solution can be adapted to use any brand of irrigation kits</td>
  </tr>
  <tr>
    <td class="tg-0lax">Standard hose pipe</td>
    <td class="tg-0lax"/>
    <td class="tg-0lax">Misc. lengths of hose pipe to connect everything up</td>
  </tr>
</tbody></table>
<!--kg-card-end: html-->
<hr><h3 id="%E2%9A%A0%EF%B8%8F-safety-first">⚠️ Safety First</h3><p>Even though this project mostly uses low voltage, it still involves water! <strong>Electrical safety still matters</strong>:</p><ul><li>Double-check power ratings of relays and valves.</li><li>Keep low-voltage and mains-voltage circuits physically separate.</li><li>If you’re not confident with wiring, get help from someone experienced.</li><li>Always test with the water supply <strong>off</strong> the first time you power it up.</li><li>Always test the water fittings without the controller being connected to mains power!</li></ul><p>A small amount of planning here avoids fried boards (or flooded flowerbeds) later.</p><hr><h2 id="wiring-overview">Wiring Overview</h2><ul><li>The core idea is simple:</li><li>Each relay controls one irrigation <strong>zone</strong> (valve).</li><li>When the relay energizes, it completes the 24 VAC circuit to that valve.</li><li>The ESP sends a HIGH or LOW signal to trigger the relay.</li></ul><figure class="kg-card kg-code-card"><pre><code class="language-text">[ESP32] ---&gt; [Relay1] ---&gt; Zone 1 Valve  
        ---&gt; [Relay2] ---&gt; Zone 2 Valve  
        ---&gt; [Relay3] ---&gt; Zone 3 Valve  
        ---&gt; [Relay4] ---&gt; Zone 4 Valve
</code></pre><figcaption><p><span style="white-space: pre-wrap;">Diagram (simplified)</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-text">ESP8266 GPIO → Relay Input → Relay Output → Solenoid Valve → 24 VAC Transformer</code></pre><figcaption><p><span style="white-space: pre-wrap;">Basic flow for controlling a zone</span></p></figcaption></figure><p>Label each zone clearly (Zone 1 – Front Garden, Zone 2 – Lawn, etc.). It makes automation easier down the road.</p><hr><h3 id="planning-the-zones">Planning the Zones</h3><p>Think through how you want to divide watering areas:</p><ul><li><strong>Zone 1</strong> – Front garden</li><li><strong>Zone 2</strong> – Back lawn</li><li><strong>Zone 3</strong> – Vegetable bed</li><li><strong>Zone 4</strong> – Pots / greenhouse</li></ul><p>The beauty of this project is you can add/remove zones as required at anytime, you can even expand this further to include even more zones should it be required.</p><p>Even if you wanted another controller in another part of the garden, its easy and cheap to replicate, compared to commercial offerings.</p><p>I started with one zone and build out the system at a later date to accommodate four zones.</p><p>If you plan ahead, you can also size your transformer and relay contacts properly for the total potential current draw.</p><hr><h3 id="next-up">Next Up</h3><p>In <strong>Part 2</strong>, we’ll:</p><ul><li>Build a prototype solution using one zone</li><li>Flash ESPHome onto the board</li><li>Write a simple YAML config for up to four relays</li><li>Expose each zone as a switch in Home Assistant</li></ul><p>By the end of Part 2, you’ll be able to toggle each valve manually from your Home Assistant dashboard.</p><p/><h2 id=""/></hr></hr></hr></hr></hr></hr></hr></hr>]]></content:encoded></item><item><title><![CDATA[Tracking Daily Power Usage and Cost with Shelly and Home Assistant - Part 2]]></title><description><![CDATA[One of the most rewarding parts of home automation is getting insights into how your home uses energy — and how much it’s costing you. If you’ve ever wondered what your washing machine, PC, or server rack really costs to run, you can easily find out using Shelly smart devices and Home Assistant.

In this post, we’ll look at how to set up Shelly devices to monitor power usage, track daily totals with Home Assistant’s Utility Meter, and calculate energy costs automatically.


Tutorial parts breakd]]></description><link>https://brightbot.co.uk/tracking-daily-power-usage-and-cost-with-shelly-and-home-assistant-part-2/</link><guid isPermaLink="false">Ghost__Post__68e1607b64bc86d5eba400ca</guid><category><![CDATA[Home Automation]]></category><dc:creator><![CDATA[Andrew Townsend]]></dc:creator><pubDate>Sat, 04 Oct 2025 18:20:35 GMT</pubDate><media:content url="https://content.brightbot.co.uk/2025/10/Shellyrelays.png" medium="image"/><content:encoded><![CDATA[<img src="https://content.brightbot.co.uk/2025/10/Shellyrelays.png" alt="Tracking Daily Power Usage and Cost with Shelly and Home Assistant - Part 2"/><p>One of the most rewarding parts of home automation is getting insights into how your home uses energy — and how much it’s costing you. If you’ve ever wondered what your washing machine, PC, or server rack really costs to run, you can easily find out using <strong>Shelly smart devices</strong> and <strong>Home Assistant</strong>.</p><p>In this post, we’ll look at how to set up Shelly devices to monitor power usage, track daily totals with Home Assistant’s <strong>Utility Meter</strong>, and calculate <strong>energy costs</strong> automatically.</p><hr><h2 id="tutorial-parts-breakdown">Tutorial parts breakdown</h2><ul><li><a href="https://ghost.atathome.me/tracking-daily-power-usage-with-shelly-and-home-assistant-part-1/" rel="noreferrer">Part 1</a> - (This post) Covering basic shelly setup along with configuring Home assistant utility meters to track periodic consumption daily/weekly etc.</li><li><a href="https://ghost.atathome.me/tracking-daily-power-usage-and-cost-with-shelly-and-home-assistant-part-2/" rel="noreferrer">Part 2</a> - (This Post) Covering device cost tracking, building upon Part 1 and extending this to track daily, weekly costs</li></ul><hr><h2 id="why-shelly">Why Shelly?</h2><p>Shelly devices are compact, Wi-Fi–enabled smart switches and relays made by <a href="https://shelly.cloud/" rel="noreferrer"><strong>Allterco Robotics</strong></a>. What makes them great for energy tracking is that many models (like the <strong>Shelly 2PM</strong>, , <strong>Shelly 1PM Mini Gen3</strong>, <strong>Shelly Plug S</strong>, or <strong>Shelly EM</strong>) include built-in <strong>power monitoring</strong>.</p><p>They’re:</p><ul><li><strong>Local-first</strong> (no cloud required).</li><li><strong>Affordable</strong> and easy to retrofit.</li><li><strong>Highly compatible</strong> with Home Assistant.</li><li><strong>Reliable</strong> for continuous energy measurement.</li></ul><p>If you’re already running Home Assistant, Shelly is one of the most seamless ways to get per-device power metrics.</p><hr><h2 id="a-quick-note-on-electrical-safety-%E2%9A%A0%EF%B8%8F">A Quick Note on Electrical Safety ⚠️</h2><p>Before diving into wiring any Shelly devices, it’s really important to emphasise <strong>electrical safety</strong>.</p><p>If you’re installing in-line devices like a Shelly 1PM or 2.5 — anything that involves mains voltage wiring — <strong>make sure you fully understand what you’re doing</strong>.</p><ul><li>Always <strong>turn off power</strong> at the breaker before working.</li><li>Use proper tools and insulation.</li><li>If you’re not confident with electrical work, it’s best to get help from a <strong>qualified electrician</strong>.</li><li>Ensure you have reviewed the manufacturer guidelines and specifications, Shelly are very good at this and link all of the documentation alongside their models. <strong>Not all</strong> relays are suitable for certain load types i.e inductive vs resistive loads!</li></ul><p>Smart home projects are great fun and rewarding to learn from, but safety should always come first — no automation is worth a shock or a fire hazard.</p><hr><h2 id="hardware-setup-adding-shelly-to-home-assistant">Hardware Setup &amp; Adding Shelly to Home Assistant</h2><p>We covered this in the previous post which went step by step through getting up to this point. Along with setting up the utility meters.</p><hr><h2 id="adding-cost-tracking-%F0%9F%92%B0">Adding Cost Tracking 💰</h2><p>To track cost, you can create <strong>template sensors</strong> that multiply energy usage by your electricity rate. These are in the <code>sensors.yaml</code> file in the home directory of your Home Assistant installation, this may not exist so you might have to create it.</p><p>I have the benefit of having my In Home Display (IHD) feed data into Home assistant via MQTT which provides my import rate per kWh of electric, however if you know this value you can setup a helper which stores the current price per kWh which I will use in the example below.</p><p>For example, if your electricity costs £0.30 per kWh, </p><figure class="kg-card kg-code-card"><pre><code class="language-yaml"># Platform: template
- platform: template
  sensors:

    # microwave usage cost tracking today
    microwave_switch_electric_cost_today:
      friendly_name: "Microwave Switch Cost Today"
      unique_id: microwave_switch_electric_cost_today
      unit_of_measurement: "GBP"
      icon_template: mdi:currency-gbp
      value_template: &gt;

        {% set todayPower = states('sensor.sensor.microwave_switch_power_today') %}

        {% if todayPower == 'unknown' or todayPower == 'unavailable' %}
          {% set todayPower = 0 | float %}
        {% endif %}


        {{ '%0.2f' % ((todayPower | float) * 0.30 | float) }}

    # microwave usage cost tracking week
    microwave_switch_electric_cost_week:
      friendly_name: "Microwave Switch Cost Week"
      unique_id: microwave_switch_electric_cost_week
      unit_of_measurement: "GBP"
      icon_template: mdi:currency-gbp
      value_template: &gt;

        {% set todayPower = states('sensor.sensor.microwave_switch_power_today') %}

        {% if todayPower == 'unknown' or todayPower == 'unavailable' %}
          {% set todayPower = 0 | float %}
        {% endif %}


        {{ '%0.2f' % ((todayPower | float) * 0.30 | float) }}</code></pre><figcaption><p><span style="white-space: pre-wrap;">Snipper from Home assistant home directory </span><code spellcheck="false" style="white-space: pre-wrap;"><span>sensors.yaml</span></code></p></figcaption></figure><p>When you have saved this file, head to <strong>Developer tools</strong> -&gt; <strong>Check configuration</strong>, assuming this gives you the green light of <code>Configuration will not prevent Home Assistant from starting!</code> you can simply click the <code>Template entities</code> button to load/reload them into Home Assistant.</p><p>This will give you two new entities:</p><ul><li><code>sensor.microwave_switch_electric_cost_today</code></li><li><code>sensor.microwave_switch_electric_cost_week</code></li></ul><p>As your utility meters update, these cost sensors will automatically calculate your spending based on current usage.</p><p>For more information on Template Sensors you can find the official documentation for them here: <a href="https://www.home-assistant.io/integrations/template/#sensor">https://www.home-assistant.io/integrations/template/#sensor</a></p><hr><h2 id="visualising-energy-and-cost">Visualising Energy and Cost</h2><p>Once your sensors are in place, you can add a card to your Home Assistant dashboard:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">type: vertical-stack
cards:
  - type: statistics-graph
    title: Daily Energy Usage
    entities:
      - sensor.microwave_switch_power_today
    chart_type: bar
    days_to_show: 7
    stat_types:
      - sum
  - type: entities
    title: Today's Cost
    entities:
      - entity: sensor.microwave_switch_electric_cost_today
        name: Microwave Cost Today
</code></pre><figcaption><p><span style="white-space: pre-wrap;">Home assistant dashboard card showing statistics for energy usage and cost of that entity today</span></p></figcaption></figure><p>You’ll get a graph showing usage over the last week and a live readout of today’s running cost.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://content.brightbot.co.uk/2025/10/image-5.png" class="kg-image" alt="Tracking Daily Power Usage and Cost with Shelly and Home Assistant - Part 2" loading="lazy" width="956" height="384"><figcaption><span style="white-space: pre-wrap;">Example dashboard card showing power usage and cost today</span></figcaption></img></figure><hr><h2 id="going-further">Going Further</h2><ul><li>Use <strong>Shelly EM or 3EM</strong> to track total household energy usage.</li><li>Add <strong>automations</strong> to alert you if an appliance exceeds a certain daily cost.</li><li>Combine with <strong>InfluxDB + Grafana</strong> for long-term cost trends and forecasts.</li><li>Adjust your rate dynamically if your energy provider uses time-of-day pricing.</li></ul><hr><h2 id="conclusion">Conclusion</h2><p>With just a few Shelly devices and some Home Assistant YAML, you can turn your smart plugs into a detailed, cost-aware energy dashboard.</p><p>You’ll not only see <em>how much power</em> your devices use, but also <em>how much it costs</em> — giving you real insight into your home’s efficiency and helping you make smarter energy decisions.</p><p>Just remember: when dealing with mains wiring, <strong>take electrical safety seriously</strong> — measure twice, wire once, and if in doubt, call an electrician.</p><p>It’s one of those simple automations that pays for itself — literally.</p></hr></hr></hr></hr></hr></hr></hr></hr>]]></content:encoded></item><item><title><![CDATA[Tracking Daily Power Usage with Shelly and Home Assistant - Part 1]]></title><description><![CDATA[One of the best parts of home automation is getting real data about how your home works — not just controlling things, but understanding them. If you’ve ever wondered how much energy your appliances are actually using, or wanted to see your daily consumption trends, you can do that easily with Shelly smart devices and Home Assistant.

In this post, I’ll walk through how to integrate Shelly devices with Home Assistant and use them to track and visualise your daily power usage.


Tutorial parts br]]></description><link>https://brightbot.co.uk/tracking-daily-power-usage-with-shelly-and-home-assistant-part-1/</link><guid isPermaLink="false">Ghost__Post__68e1291d64bc86d5eba40097</guid><category><![CDATA[Home Automation]]></category><dc:creator><![CDATA[Andrew Townsend]]></dc:creator><pubDate>Sat, 04 Oct 2025 14:28:21 GMT</pubDate><media:content url="https://content.brightbot.co.uk/2025/10/Shellyrelays.png" medium="image"/><content:encoded><![CDATA[<img src="https://content.brightbot.co.uk/2025/10/Shellyrelays.png" alt="Tracking Daily Power Usage with Shelly and Home Assistant - Part 1"/><p>One of the best parts of home automation is getting real data about how your home works — not just controlling things, but <em>understanding</em> them. If you’ve ever wondered how much energy your appliances are actually using, or wanted to see your daily consumption trends, you can do that easily with <strong>Shelly smart devices</strong> and <strong>Home Assistant</strong>.</p><p>In this post, I’ll walk through how to integrate Shelly devices with Home Assistant and use them to track and visualise your daily power usage.</p><hr><h2 id="tutorial-parts-breakdown">Tutorial parts breakdown</h2><ul><li><a href="https://ghost.atathome.me/tracking-daily-power-usage-with-shelly-and-home-assistant-part-1/" rel="noreferrer">Part 1</a> - (This post) Covering basic shelly setup along with configuring Home assistant utility meters to track periodic consumption daily/weekly etc.</li><li><a href="https://ghost.atathome.me/tracking-daily-power-usage-and-cost-with-shelly-and-home-assistant-part-2/" rel="noreferrer">Part 2</a> - Covering device cost tracking, building upon Part 1 and extending this to track daily, weekly costs</li></ul><hr><h2 id="why-shelly">Why Shelly?</h2><p>Shelly devices are compact, Wi-Fi–enabled smart switches and relays made by <a href="https://shelly.cloud/" rel="noreferrer"><strong>Allterco Robotics</strong></a>. What makes them great for energy tracking is that many models (like the <strong>Shelly 2PM</strong>, , <strong>Shelly 1PM Mini Gen3</strong>, <strong>Shelly Plug S</strong>, or <strong>Shelly EM</strong>) include built-in <strong>power monitoring</strong>.</p><p>They’re:</p><ul><li><strong>Local-first</strong> (no cloud required).</li><li><strong>Affordable</strong> and easy to retrofit.</li><li><strong>Highly compatible</strong> with Home Assistant.</li><li><strong>Reliable</strong> for continuous energy measurement.</li></ul><p>If you’re already running Home Assistant, Shelly is one of the most seamless ways to get per-device power metrics.</p><hr><h2 id="a-quick-note-on-electrical-safety-%E2%9A%A0%EF%B8%8F">A Quick Note on Electrical Safety ⚠️</h2><p>Before diving into wiring any Shelly devices, it’s really important to emphasise <strong>electrical safety</strong>.</p><p>If you’re installing in-line devices like a Shelly 1PM or 2.5 — anything that involves mains voltage wiring — <strong>make sure you fully understand what you’re doing</strong>.</p><ul><li>Always <strong>turn off power</strong> at the breaker before working.</li><li>Use proper tools and insulation.</li><li>If you’re not confident with electrical work, it’s best to get help from a <strong>qualified electrician</strong>.</li><li>Ensure you have reviewed the manufacturer guidelines and specifications, Shelly are very good at this and link all of the documentation alongside their models. <strong>Not all</strong> relays are suitable for certain load types i.e inductive vs resistive loads!</li></ul><p>Smart home projects are great fun and rewarding to learn from, but safety should always come first — no automation is worth a shock or a fire hazard.</p><hr><h2 id="hardware-setup">Hardware Setup</h2><p>For this example, you can use any of the following Shelly devices:</p><ul><li>🧱 <strong>Shelly 1PM / 2.5</strong> – inline relays with power monitoring for lights or sockets.</li><li>🔌 <strong>Shelly Plug S / Plug UK</strong> – smart plugs for standalone devices.</li><li>⚡ <strong>Shelly EM / 3EM</strong> – clamp meters for whole-circuit or multi-phase monitoring.</li></ul><p>Connect them to your Wi-Fi and assign static IPs if possible — it helps with reliability.</p><hr><h2 id="adding-shelly-to-home-assistant">Adding Shelly to Home Assistant</h2><p>You have two main options for integration:</p><h3 id="1-native-shelly-integration-recommended">1. <strong>Native Shelly Integration (Recommended)</strong></h3><p>Home Assistant natively supports Shelly devices using the <strong>CoAP</strong> or <strong>MQTT</strong> protocol.</p><ol><li>In Home Assistant, go to <strong>Settings → Devices &amp; Services → Add Integration</strong>.</li><li>Search for <strong>Shelly</strong>.</li><li>It should auto-discover devices on your network.</li><li>Click <strong>Configure</strong> to add them.</li></ol><p>Once connected, you’ll see new entities like:</p><ul><li><code>sensor.shelly_plug_power</code> (instantaneous power, W)</li><li><code>sensor.shelly_plug_energy</code> (total energy, Wh or kWh)<ul><li>This is total energy over all-time rather than today/yesterday like other devices such as Sonoff / Tasmota, so Shelly requires a bit more setup to get Periodic data (Daily, Weekly etc)</li></ul></li><li><code>switch.shelly_plug_relay</code> (on/off control)</li></ul><h3 id="2-mqtt-integration-optional">2. <strong>MQTT Integration</strong> (Optional)</h3><p>If you already use MQTT, you can flash Shelly devices to use it directly. Home Assistant will automatically create entities for MQTT topics like <code>shellies/shellyplug/emeter/0/power</code>.</p><hr><h2 id="tracking-daily-power-usage">Tracking Daily Power Usage</h2><p>Now that your Shelly devices are feeding data into Home Assistant, let’s turn that into meaningful daily stats.</p><p>You can use Home Assistant’s built-in <a href="https://www.home-assistant.io/integrations/utility_meter/" rel="noreferrer"><strong>Utility Meter</strong></a> integration to automatically track daily, weekly, or monthly energy totals.</p><h3 id="example-configuration">Example Configuration</h3><p>In your <code>configuration.yaml</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">...
utility_meter:
  microwave_switch_power_today:
    source: sensor.microwave_switch_power
    cycle: daily
  microwave_switch_power_weekly:
    source: sensor.microwave_switch_power
    cycle: monthly
...</code></pre><figcaption><p><span style="white-space: pre-wrap;">Example utility meters in Home Assistant configuration.yaml</span></p></figcaption></figure><p>This creates two new entities:</p><ul><li><code>sensor.microwave_switch_power_today</code></li><li><code>sensor.microwave_switch_power_weekly</code></li></ul><p>Each resets automatically at the start of a new day or month.</p><hr><h2 id="visualizing-in-the-dashboard">Visualizing in the Dashboard</h2><p>Once you’ve got your utility meter entities, you can create a <strong>Lovelace dashboard</strong> card:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">type: statistics-graph
title: Daily Energy Usage
entities:
  - sensor.microwave_switch_power_today
  - sensor.tv_daily
chart_type: bar
days_to_show: 7
stat_types:
  - sum</code></pre><figcaption><p><span style="white-space: pre-wrap;">Home assistant dashboard card showing statistics for energy meters</span></p></figcaption></figure><p>Now you’ll have a bar chart showing daily consumption per device — great for spotting trends and understanding where your power goes.</p><hr><h2 id="bonus-alerts-automation">Bonus: Alerts &amp; Automation</h2><p>You can also use automations to warn you about unusual power usage or when an appliance finishes running. For example:</p><p><strong>Notify when washing machine is done:</strong></p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">alias: Microwave Finished
trigger:
  - platform: numeric_state
    entity_id: sensor.microwave_switch_power
    below: 2
    for:
      minutes: 3
action:
  - service: notify.mobile_app_your_phone
    data:
      message: "Microwave cycle completed!"</code></pre><figcaption><p><span style="white-space: pre-wrap;">Home assistant automation detecting when a device has finished its cycle</span></p></figcaption></figure><p>A simple and unnecessary but very satisfying automation.</p><hr><h2 id="conclusion">Conclusion</h2><p>Shelly devices + Home Assistant = a powerful combo for understanding your home’s energy use.</p><p>With just a few sensors and a couple of YAML lines, you can build detailed daily usage charts, track trends over time, and even trigger automations based on real power data.</p><p>Whether you’re trying to save energy, monitor appliance efficiency, or just geek out on data, this setup is one of the most practical additions to any home lab.</p></hr></hr></hr></hr></hr></hr></hr></hr></hr>]]></content:encoded></item><item><title><![CDATA[Building a Home Lab Dashboard with Homer]]></title><description><![CDATA[If you’ve been running a home lab for a while, you’ve probably collected a fair number of services — things like Home Assistant, Pi-hole, Proxmox, Uptime Kuma, Grafana, media servers, and more. The problem is, remembering all those URLs and ports gets messy fast.

That’s where Homer comes in: a simple, customizable static dashboard you can host anywhere to keep your home lab neat and organized.


What is Homer?

Homer is a lightweight homepage for your self-hosted services. It’s just a single-pa]]></description><link>https://brightbot.co.uk/building-a-home-lab-dashboard-with-homer/</link><guid isPermaLink="false">Ghost__Post__68e038bb64bc86d5eba40059</guid><category><![CDATA[Home Lab]]></category><category><![CDATA[Infrastructure]]></category><dc:creator><![CDATA[Andrew Townsend]]></dc:creator><pubDate>Fri, 03 Oct 2025 21:14:20 GMT</pubDate><media:content url="https://content.brightbot.co.uk/2025/10/Screenshot-2025-10-03-at-22.14.04.png" medium="image"/><content:encoded><![CDATA[<img src="https://content.brightbot.co.uk/2025/10/Screenshot-2025-10-03-at-22.14.04.png" alt="Building a Home Lab Dashboard with Homer"/><p>If you’ve been running a home lab for a while, you’ve probably collected a fair number of services — things like Home Assistant, Pi-hole, Proxmox, Uptime Kuma, Grafana, media servers, and more. The problem is, remembering all those URLs and ports gets messy fast.</p><p>That’s where <a href="https://github.com/bastienwirtz/homer" rel="noopener"><strong>Homer</strong></a> comes in: a simple, customizable <strong>static dashboard</strong> you can host anywhere to keep your home lab neat and organized.</p><hr><h2 id="what-is-homer">What is Homer?</h2><p>Homer is a <strong>lightweight homepage</strong> for your self-hosted services. It’s just a single-page web app written in Vue.js that you can run with Docker, Nginx, or even a static file host.</p><p>Key features:</p><ul><li><strong>Config-driven</strong>: Simple YAML config file for customisation.</li><li><strong>Beautiful UI</strong>: Clean, tile-based interface with icons and colours.</li><li><strong>Group support</strong>: Organise services into categories (e.g., Media, Monitoring, Infrastructure).</li><li><strong>Custom links and widgets</strong>: Add search bars, markdown notes, or quick links.</li><li><strong>Portable</strong>: No database — just config and assets.</li></ul><p>It’s like having your own start page for everything in your lab.</p><hr><h2 id="why-use-homer-in-a-home-lab">Why Use Homer in a Home Lab?</h2><ul><li><strong>Central access point</strong> for all your services.</li><li><strong>Easy to remember</strong> one URL (like <code>http://homer.local</code>) instead of 20 different ports.</li><li><strong>Customizable look and feel</strong> with icons and branding.</li><li><strong>Lightweight</strong> — runs anywhere without heavy dependencies.</li></ul><hr><h2 id="installing-homer">Installing Homer</h2><h3 id="using-docker">Using Docker</h3><p>The easiest way to get Homer running is with Docker:</p><pre><code class="language-bash">docker run -d \
  -p 8080:8080 \
  -v /path/to/config:/www/assets \
  b4bz/homer:latest</code></pre><ul><li>Mount a volume for your config at <code>/www/assets</code>.</li><li>Access Homer at <code>http://your-server:8080</code>.</li></ul><h3 id="using-proxmox-lxc">Using Proxmox LXC</h3><p>Homer is even easier to setup in Proxmox using a LXC Container, there is a community script available and its instructions can be found here:<br><a href="https://community-scripts.github.io/ProxmoxVE/scripts?id=homer">https://community-scripts.github.io/ProxmoxVE/scripts?id=homer</a></br></p><p>Always good to review scripts before blindly running them on your hardware but its as simple as running the following script and following the steps in the console</p><pre><code class="language-bash">bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/homer.sh)"</code></pre><hr><h2 id="configuring-homer">Configuring Homer</h2><p>Homer is configured via a single YAML file: <code>config.yml</code>.</p><p>Here’s an example:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">title: "Home Lab Dashboard"
subtitle: "All my services in one place"
logo: "assets/logo.png"

links:
  - name: "GitHub"
    url: "https://github.com"

services:
  - name: "Media"
    icon: "fas fa-film"
    items:
      - name: "Plex"
        logo: "assets/icons/plex.png"
        url: "http://192.168.1.10:32400"
      - name: "Jellyfin"
        logo: "assets/icons/jellyfin.png"
        url: "http://192.168.1.11:8096"

  - name: "Monitoring"
    icon: "fas fa-chart-line"
    items:
      - name: "Grafana"
        logo: "assets/icons/grafana.png"
        url: "http://192.168.1.12:3000"
      - name: "Uptime Kuma"
        logo: "assets/icons/uptime-kuma.png"
        url: "http://192.168.1.13:3001"
</code></pre><figcaption><p dir="ltr"><span style="white-space: pre-wrap;">homer config.yml example</span></p></figcaption></figure><p>You can even do things such as integrating with specific applications such as Proxmox  to get disk usage etc, this is done with smart cards.</p><p>See my image below or handily get it to ping resources to check they are online/offline, this can be achieved with the following snippet as an example.</p><pre><code class="language-yaml">- name: "Plex"
  logo: "https://i.redd.it/5x93lknmuaw81.jpg"
  subtitle: "https://plex.your-domain.com"
  url: "https://plex.your-domain.com/"
  type: Ping
  successCodes: [200, 401]
  method: "get"
  target: "_blank"</code></pre><p>For a full list of available Smart cards these are included in the Homer documentation here including a few I have mentioned above:</p><ul><li><a href="https://github.com/bastienwirtz/homer/blob/main/docs/customservices.md">https://github.com/bastienwirtz/homer/blob/main/docs/customservices.md</a></li><li><a href="https://github.com/bastienwirtz/homer/blob/main/docs/customservices.md#proxmox">https://github.com/bastienwirtz/homer/blob/main/docs/customservices.md#proxmox</a></li><li><a href="https://github.com/bastienwirtz/homer/blob/main/docs/customservices.md#ping">https://github.com/bastienwirtz/homer/blob/main/docs/customservices.md#ping</a></li></ul><hr><h2 id="customising-the-dashboard">Customising the Dashboard</h2><p>Some nice touches you can add:</p><ul><li><strong>Icons and logos</strong>: Drop PNG/SVGs into the assets folder.</li><li><strong>Themes</strong>: Light, dark, and custom themes supported.</li><li><strong>Widgets</strong>: Add search bars (Google, DuckDuckGo, internal docs) or markdown notes.</li><li><strong>External links</strong>: Quick access to GitHub, documentation, or cloud dashboards.</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://content.brightbot.co.uk/2025/10/image-4.png" class="kg-image" alt="Building a Home Lab Dashboard with Homer" loading="lazy" width="3404" height="1508"><figcaption><span style="white-space: pre-wrap;">Example customised dashboard</span></figcaption></img></figure><hr><h2 id="tips-for-home-lab-use">Tips for Home Lab Use</h2><ul><li>Host Homer on a reverse proxy (like HAproxy or Nginx or Traefik) at <code>http://homer.your-domain</code>.</li><li>Use HTTPS with Let’s Encrypt if exposing outside your LAN.</li><li>Combine with authentication (Authelia, OAuth, etc.) for secure access or keep it hidden and internal use only.</li><li>Keep a <strong>backup of your config.yml</strong> — that’s your whole dashboard.</li></ul><hr><h2 id="conclusion">Conclusion</h2><p><strong>Homer</strong> is a simple, elegant way to bring order to your home lab chaos. With one URL, you get a beautiful dashboard that keeps all your services accessible and organised. It’s lightweight, easy to configure, and endlessly customisable.</p><p>If your browser bookmarks are overflowing or you’ve ever forgotten which port a service runs on, Homer is the perfect solution.</p><hr><h2 id="references">References</h2><ul><li><a href="https://github.com/louislam/uptime-kuma">https://github.com/louislam/uptime-kuma</a></li></ul><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/louislam/uptime-kuma"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - louislam/uptime-kuma: A fancy self-hosted monitoring tool</div><div class="kg-bookmark-description">A fancy self-hosted monitoring tool. Contribute to louislam/uptime-kuma development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://static.ghost.org/v5.0.0/images/link-icon.svg" alt="Building a Home Lab Dashboard with Homer"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">louislam</span></img></div></div><div class="kg-bookmark-thumbnail"><img src="https://repository-images.githubusercontent.com/382496361/f21b61f5-693a-4925-9f1f-fea237ade223" alt="Building a Home Lab Dashboard with Homer" onerror="this.style.display = 'none'"/></div></a></figure><ul><li><a href="https://community-scripts.github.io/ProxmoxVE/scripts?id=uptimekuma">https://community-scripts.github.io/ProxmoxVE/scripts?id=uptimekuma</a></li></ul><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://community-scripts.github.io/ProxmoxVE/scripts?id=uptimekuma"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Proxmox VE Helper-Scripts</div><div class="kg-bookmark-description">The official website for the Proxmox VE Helper-Scripts (Community) repository. Featuring over 300+ scripts to help you manage your Proxmox Virtual Environment.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://static.ghost.org/v5.0.0/images/link-icon.svg" alt="Building a Home Lab Dashboard with Homer"><span class="kg-bookmark-author">Proxmox VE Helper-Scripts</span><span class="kg-bookmark-publisher">Bram Suurd</span></img></div></div><div class="kg-bookmark-thumbnail"><img src="https://community-scripts.github.io/ProxmoxVE/defaultimg.png" alt="Building a Home Lab Dashboard with Homer" onerror="this.style.display = 'none'"/></div></a></figure></hr></hr></hr></hr></hr></hr></hr></hr>]]></content:encoded></item><item><title><![CDATA[Monitoring Services with Uptime Kuma]]></title><description><![CDATA[If you run self-hosted apps, home lab services, or production infrastructure, you know the feeling: is it just me, or is the service down? Instead of waiting for someone to complain (or discovering it yourself when you try to use it), it’s better to have a monitoring tool that tells you when things break — and ideally before your users notice.

That’s where Uptime Kuma comes in.


What is Uptime Kuma?

Uptime Kuma is a self-hosted, open-source status monitoring tool. Think of it as a free, self-]]></description><link>https://brightbot.co.uk/monitoring-services-with-uptime-kuma/</link><guid isPermaLink="false">Ghost__Post__68e034f264bc86d5eba4003b</guid><category><![CDATA[Home Lab]]></category><category><![CDATA[Infrastructure]]></category><dc:creator><![CDATA[Andrew Townsend]]></dc:creator><pubDate>Fri, 03 Oct 2025 20:52:56 GMT</pubDate><media:content url="https://content.brightbot.co.uk/2025/10/Screenshot-2025-10-03-at-21.52.33.png" medium="image"/><content:encoded><![CDATA[<img src="https://content.brightbot.co.uk/2025/10/Screenshot-2025-10-03-at-21.52.33.png" alt="Monitoring Services with Uptime Kuma"/><p>If you run self-hosted apps, home lab services, or production infrastructure, you know the feeling: <em>is it just me, or is the service down?</em> Instead of waiting for someone to complain (or discovering it yourself when you try to use it), it’s better to have a monitoring tool that tells you when things break — and ideally before your users notice.</p><p>That’s where <a href="https://github.com/louislam/uptime-kuma" rel="noreferrer"><strong>Uptime Kuma</strong></a> comes in.</p><hr><h2 id="what-is-uptime-kuma">What is Uptime Kuma?</h2><p>Uptime Kuma is a self-hosted, open-source <strong>status monitoring tool</strong>. Think of it as a free, self-managed alternative to services like UptimeRobot or StatusCake. It lets you monitor websites, APIs, and network services, then alerts you when they go down.</p><p>Key features:</p><ul><li><strong>Multiple monitor types</strong>: HTTP(s), TCP, ICMP (ping), DNS, Push, and more.</li><li><strong>Customisable alerts</strong>: Email, Telegram, Discord, Slack, Gotify, Pushover, etc.</li><li><strong>Beautiful dashboard</strong>: A clean web interface with status history and charts.</li><li><strong>Public status pages</strong>: Share uptime with your team or community.</li><li><strong>Self-hosted</strong>: You control the data and deployment.</li></ul><hr><h2 id="why-use-uptime-kuma">Why Use Uptime Kuma?</h2><ul><li><strong>Keep an eye on home lab services</strong> (Pi-hole, Home Assistant, media servers).</li><li><strong>Monitor production apps</strong> and APIs with confidence.</li><li><strong>Get alerts quickly</strong> instead of discovering downtime by accident.</li><li><strong>Track historical uptime</strong> for troubleshooting or reporting.</li><li><strong>Run it anywhere</strong> — from a Raspberry Pi to a cloud VPS.</li></ul><hr><h2 id="installing-uptime-kuma">Installing Uptime Kuma</h2><h3 id="using-docker">Using docker</h3><p>The easiest way to get started is with Docker:</p><pre><code class="language-bash">docker run -d \
  --restart=always \
  -p 3001:3001 \
  -v uptime-kuma:/app/data \
  louislam/uptime-kuma</code></pre><p>This runs Uptime Kuma on port <code>3001</code> and persists data in a Docker volume.</p><p>Then, open <code>http://your-server:3001</code> in a browser, create an admin account, and you’re ready to go.</p><h3 id="using-proxmox-lxc">Using Proxmox LXC</h3><p>Kuma is even easier to setup in Proxmox using a LXC Container, there is a community script available and its instructions can be found here:<br><a href="https://community-scripts.github.io/ProxmoxVE/scripts?id=uptimekuma">https://community-scripts.github.io/ProxmoxVE/scripts?id=uptimekuma</a></br></p><p>Always good to review scripts before blindly running them on your hardware but its as simple as running the following script and following the steps in the console</p><pre><code class="language-bash">bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/uptimekuma.sh)"</code></pre><hr><h2 id="adding-a-monitor">Adding a Monitor</h2><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://content.brightbot.co.uk/2025/10/image-1.png" class="kg-image" alt="Monitoring Services with Uptime Kuma" loading="lazy" width="3404" height="1826"><figcaption><span style="white-space: pre-wrap;">Showing the create/edit monitor form</span></figcaption></img></figure><ol><li>Log in to the dashboard.</li><li>Click <strong>Add New Monitor</strong>.</li><li>Choose a monitor type (e.g., HTTP(s), Ping, TCP).</li><li>Enter the target URL or IP.</li><li>Configure interval, timeout, and retries.</li><li>Save — and your monitor starts running.</li></ol><p>You’ll see real-time status updates, response times, and uptime percentages.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://content.brightbot.co.uk/2025/10/image.png" class="kg-image" alt="Monitoring Services with Uptime Kuma" loading="lazy" width="3404" height="1826"><figcaption><span style="white-space: pre-wrap;">Showing the monitor once it has been setup</span></figcaption></img></figure><p>You can take this further by assigning meaningful Tags to categorise monitors such as in my example if it is a public facing endpoint or an internal endpoint.</p><hr><h2 id="setting-up-alerts">Setting Up Alerts</h2><p>Uptime Kuma supports a wide range of notifications. For example, to set up <strong>Telegram alerts</strong>:</p><ol><li>Go to <strong>Settings → Notification</strong>.</li><li>Add a new notification of type <strong>Telegram Bot</strong>.</li><li>Provide your bot token and chat ID.</li><li>Link it to one or more monitors.</li></ol><p>Now, you’ll get a Telegram message whenever a service goes down or comes back up.</p><hr><h2 id="public-status-page">Public Status Page</h2><p>One of Uptime Kuma’s nicest features is the ability to generate a <strong>public status page</strong>. This is great if you’re running services for others (or just want a clean way to check everything at a glance).</p><p>You can create multiple pages — for example:</p><ul><li><strong>Internal status page</strong> for your home lab.</li><li><strong>External status page</strong> for services your users access.</li></ul><p>Each page shows uptime graphs, response times, and the current state of your monitors.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://content.brightbot.co.uk/2025/10/Screenshot-2025-10-03-at-21.47.29.png" class="kg-image" alt="Monitoring Services with Uptime Kuma" loading="lazy" width="3404" height="1826"><figcaption><span style="white-space: pre-wrap;">An example status page once configured</span></figcaption></img></figure><hr><h2 id="conclusion">Conclusion</h2><p><strong>Uptime Kuma</strong> is an excellent, self-hosted alternative for service monitoring. It’s lightweight, feature-rich, and easy to deploy. Whether you’re running a home lab, side projects, or production systems, Kuma gives you peace of mind by watching your services and notifying you when something breaks.</p><p>If you’ve been relying on hope (or user complaints) to know when things go down, give Kuma a try — your future self will thank you.</p><hr><h2 id="references">References</h2><ul><li><a href="https://github.com/louislam/uptime-kuma">https://github.com/louislam/uptime-kuma</a></li></ul><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/louislam/uptime-kuma"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - louislam/uptime-kuma: A fancy self-hosted monitoring tool</div><div class="kg-bookmark-description">A fancy self-hosted monitoring tool. Contribute to louislam/uptime-kuma development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://static.ghost.org/v5.0.0/images/link-icon.svg" alt="Monitoring Services with Uptime Kuma"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">louislam</span></img></div></div><div class="kg-bookmark-thumbnail"><img src="https://repository-images.githubusercontent.com/382496361/f21b61f5-693a-4925-9f1f-fea237ade223" alt="Monitoring Services with Uptime Kuma" onerror="this.style.display = 'none'"/></div></a></figure><ul><li><a href="https://community-scripts.github.io/ProxmoxVE/scripts?id=uptimekuma">https://community-scripts.github.io/ProxmoxVE/scripts?id=uptimekuma</a></li></ul><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://community-scripts.github.io/ProxmoxVE/scripts?id=uptimekuma"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Proxmox VE Helper-Scripts</div><div class="kg-bookmark-description">The official website for the Proxmox VE Helper-Scripts (Community) repository. Featuring over 300+ scripts to help you manage your Proxmox Virtual Environment.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://static.ghost.org/v5.0.0/images/link-icon.svg" alt="Monitoring Services with Uptime Kuma"><span class="kg-bookmark-author">Proxmox VE Helper-Scripts</span><span class="kg-bookmark-publisher">Bram Suurd</span></img></div></div><div class="kg-bookmark-thumbnail"><img src="https://community-scripts.github.io/ProxmoxVE/defaultimg.png" alt="Monitoring Services with Uptime Kuma" onerror="this.style.display = 'none'"/></div></a></figure></hr></hr></hr></hr></hr></hr></hr></hr>]]></content:encoded></item><item><title><![CDATA[Setting Up Ghost with S3 Storage Using ghost-storage-adapter-s3: Part 2]]></title><description><![CDATA[If you haven't read Part 1 of this series it can be found here Setting Up Ghost with S3 Storage Using ghost-storage-adapter-s3: Part 1

Amazon CloudFront is a powerful CDN for speeding up content delivery. By default, when you create a distribution, AWS gives you a long, autogenerated domain like:

d1234abcdef.cloudfront.net

That works fine, but it’s not user-friendly. For production, you’ll usually want to use your own domain — for example, content.yoursite.com — so your assets are delivered u]]></description><link>https://brightbot.co.uk/setting-up-ghost-with-s3-storage-using-ghost-storage-adapter-s3-part-2/</link><guid isPermaLink="false">Ghost__Post__68deff7f64bc86d5eba3ffc3</guid><category><![CDATA[AWS]]></category><category><![CDATA[Infrastructure]]></category><category><![CDATA[Home Lab]]></category><dc:creator><![CDATA[Andrew Townsend]]></dc:creator><pubDate>Thu, 02 Oct 2025 23:01:18 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1662947368791-8630979e964b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDl8fGFtYXpvbiUyMHdlYnxlbnwwfHx8fDE3NTk0NDYyODd8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1662947368791-8630979e964b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMTc3M3wwfDF8c2VhcmNofDl8fGFtYXpvbiUyMHdlYnxlbnwwfHx8fDE3NTk0NDYyODd8MA&ixlib=rb-4.1.0&q=80&w=2000" alt="Setting Up Ghost with S3 Storage Using ghost-storage-adapter-s3: Part 2"/><p>If you haven't read Part 1 of this series it can be found here <a href="https://brightbot.co.uk/setting-up-ghost-with-s3-storage-using-ghost-storage-adapter-s3/" rel="noreferrer">Setting Up Ghost with S3 Storage Using ghost-storage-adapter-s3: Part 1</a></p><p>Amazon CloudFront is a powerful CDN for speeding up content delivery. By default, when you create a distribution, AWS gives you a long, autogenerated domain like:</p><pre><code class="language-bash">d1234abcdef.cloudfront.net</code></pre><p>That works fine, but it’s not user-friendly. For production, you’ll usually want to use your own domain — for example, <strong>content.yoursite.com</strong> — so your assets are delivered under your brand.</p><p>In this post, I’ll walk through setting up a CloudFront distribution with a <strong>custom domain alias (CNAME)</strong>.</p><hr><h2 id="prerequisites">Prerequisites</h2><ul><li>An AWS account with CloudFront enabled. (This is covered in Part 1)</li><li>A domain you own (registered with Route 53 or another DNS provider).</li><li>An SSL/TLS certificate in AWS Certificate Manager (ACM) for your domain.</li></ul><hr><p>Just like the Previous post, example AWS CloudFormation templates are available on my GitHub and can be found here and navigate to the <code>ghost-storage-adapter-s3</code> directory or using the individual links linked below. Or you can follow the steps to create it manually.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/atownsend247/brightbot-source-files/tree/main"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - atownsend247/brightbot-source-files: Provides git access to scripts and templates for associated BrightBot blog posts</div><div class="kg-bookmark-description">Provides git access to scripts and templates for associated BrightBot blog posts - atownsend247/brightbot-source-files</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://static.ghost.org/v5.0.0/images/link-icon.svg" alt="Setting Up Ghost with S3 Storage Using ghost-storage-adapter-s3: Part 2"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">atownsend247</span></img></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/48597fed9a6461baf4b9cab167d58f0c122a8fbb8b77948e99a9043df6cd8a57/atownsend247/brightbot-source-files" alt="Setting Up Ghost with S3 Storage Using ghost-storage-adapter-s3: Part 2" onerror="this.style.display = 'none'"/></div></a></figure><ul><li><a href="https://github.com/atownsend247/brightbot-source-files/blob/main/ghost-storage-adapter-s3/cloudformation-template-acm.yaml">cloudformation-template-acm.yaml</a> Stack to create the AWS Amazon Certificate Manager (ACM) Certificate in the us-east-1 region.</li><li><a href="https://github.com/atownsend247/brightbot-source-files/blob/main/ghost-storage-adapter-s3/cloudformation-template-with-acm-alias.yaml">cloudformation-template-with-acm-alias.yaml</a> An update template from Part 1, that includes a custom CloudFront alias and certificate configuration.</li></ul><h2 id="step-1-request-an-ssltls-certificate">Step 1: Request an SSL/TLS Certificate</h2><p>To use a custom domain with CloudFront, you need an SSL certificate.</p><ol><li>Request a public certificate for your custom domain (e.g., <code>content.yoursite.com</code>).</li><li>Validate the certificate using DNS or email (DNS is recommended).</li></ol><p>Go to <strong>AWS Certificate Manager</strong> in the <strong>us-east-1 (N. Virginia)</strong> region.</p><blockquote>Important: CloudFront only uses certificates in <strong>us-east-1</strong>, even if your distribution is elsewhere.</blockquote><p>Once validated, your certificate will show as <strong>Issued</strong>.</p><hr><h2 id="step-2-create-or-edit-a-cloudfront-distribution">Step 2: Create or Edit a CloudFront Distribution</h2><ol><li>Go to <strong>CloudFront &gt; Distributions</strong> and click <strong>Create Distribution</strong> (or edit an existing one).</li><li>Under <strong>Settings → Alternate Domain Names (CNAMEs)</strong>, enter your custom domain:</li></ol><pre><code class="language-bash">content.yoursite.com
</code></pre><ol start="3"><li>Under <strong>Custom SSL Certificate</strong>, select the certificate you created in ACM.</li><li>Configure the rest of your distribution (origin, caching, behaviors) as usual.</li><li>Save and deploy the distribution.</li></ol><hr><h2 id="step-3-update-your-dns">Step 3: Update Your DNS</h2><p>Now you need to point your custom domain to the CloudFront distribution.</p><ol><li>Go to your DNS provider (Route 53 or elsewhere).</li><li>Create a <strong>CNAME record</strong> for your custom domain:</li></ol><pre><code class="language-bash">content.yoursite.com → d1234abcdef.cloudfront.net</code></pre><ol start="3"><li>Save the record.</li></ol><p>DNS changes may take some time to propagate.</p><hr><h2 id="step-4-test-the-setup">Step 4: Test the Setup</h2><p>Once DNS propagation is complete:</p><ul><li>Visit <code>https://content.yoursite.com</code> in your browser.</li><li>You should see content served from CloudFront.</li><li>Check the SSL certificate — it should show your custom domain.</li></ul><hr><h2 id="step-5-update-configuration-of-ghost-to-use-the-adapter">Step 5: Update configuration of Ghost to Use the Adapter</h2><p>In your Ghost config file (<code>config.production.json</code>), update the storage block <strong>assetHost</strong> property:</p><pre><code class="language-json">{
  ...,
  "storage": {
    "active": "s3",
    "s3": {
      ...
      "assetHost": "https://content.yoursite.com",
      ...
    }
  }
}</code></pre><p>Save the config file and restart your Ghost instance to apply changes:</p><pre><code class="language-bash">ghost restart</code></pre><p>From now on, uploaded images and media will go directly to your S3 bucket instead of local disk.</p><hr><h2 id="tips-best-practices">Tips &amp; Best Practices</h2><ul><li><strong>Use Route 53 with Alias Records</strong>: If your custom domain is a root domain (<code>example.com</code> instead of <code>cdn.example.com</code>), use an Alias record in Route 53 instead of CNAME.</li><li><strong>Leverage a CDN subdomain</strong>: Keeping CDN assets on <code>cdn.yoursite.com</code> helps separate them from your main domain.</li><li><strong>Enable logging</strong>: CloudFront can log requests to S3 for debugging and analytics.</li><li><strong>Cache policies</strong>: Fine-tune cache behavior to balance performance and freshness.</li></ul><hr><h2 id="conclusion">Conclusion</h2><p>Setting up a CloudFront distribution with a <strong>custom domain alias</strong> gives you:</p><ul><li>A cleaner, branded URL for your assets.</li><li>SSL/TLS encryption with your own domain certificate.</li><li>The speed and scalability of AWS CloudFront.</li></ul><p>Once configured, your site looks more professional and benefits from faster, more secure content delivery.</p><p>This tutorial has talked you through fully configuring Ghost to use an external storage adapter utilising Amazon S3, Amazon CloudFront, Amazon ACM Certificate.</p><hr><h2 id="other-posts-in-this-series">Other posts in this series</h2><ul><li><a href="https://brightbot.co.uk/setting-up-ghost-with-s3-storage-using-ghost-storage-adapter-s3/" rel="noreferrer">Setting Up Ghost with S3 Storage Using ghost-storage-adapter-s3: Part 1</a></li><li><a href="https://brightbot.co.uk/setting-up-ghost-with-s3-storage-using-ghost-storage-adapter-s3-part-2/" rel="noreferrer">Setting Up Ghost with S3 Storage Using ghost-storage-adapter-s3: Part 2</a></li></ul></hr></hr></hr></hr></hr></hr></hr></hr></hr>]]></content:encoded></item><item><title><![CDATA[Setting Up Ghost with S3 Storage Using ghost-storage-adapter-s3: Part 1]]></title><description><![CDATA[By default, Ghost stores uploaded images and media locally on the same server that runs Ghost. That works fine for small blogs, but if you want to run Ghost in a scalable environment (like Docker, Kubernetes, or multiple servers behind a load balancer), you’ll quickly run into problems.

To make Ghost more flexible, you can use Amazon S3 (or an S3-compatible service like MinIO, DigitalOcean Spaces, or Backblaze B2) for media storage. This decouples your storage from the Ghost instance, ensuring ]]></description><link>https://brightbot.co.uk/setting-up-ghost-with-s3-storage-using-ghost-storage-adapter-s3/</link><guid isPermaLink="false">Ghost__Post__68de512573be1314e6d24b0e</guid><category><![CDATA[AWS]]></category><category><![CDATA[Infrastructure]]></category><category><![CDATA[Home Lab]]></category><dc:creator><![CDATA[Andrew Townsend]]></dc:creator><pubDate>Thu, 02 Oct 2025 14:32:08 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1662947368907-808ab49b9495?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDN8fGFtYXpvbiUyMHdlYiUyMHNlcnZpY2VzfGVufDB8fHx8MTc1OTQxODc2OHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1662947368907-808ab49b9495?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMTc3M3wwfDF8c2VhcmNofDN8fGFtYXpvbiUyMHdlYiUyMHNlcnZpY2VzfGVufDB8fHx8MTc1OTQxODc2OHww&ixlib=rb-4.1.0&q=80&w=2000" alt="Setting Up Ghost with S3 Storage Using ghost-storage-adapter-s3: Part 1"/><p>By default, <a rel="noopener">Ghost</a> stores uploaded images and media locally on the same server that runs Ghost. That works fine for small blogs, but if you want to run Ghost in a <strong>scalable environment</strong> (like Docker, Kubernetes, or multiple servers behind a load balancer), you’ll quickly run into problems.</p><p>To make Ghost more flexible, you can use <strong>Amazon S3 (or an S3-compatible service like MinIO, DigitalOcean Spaces, or Backblaze B2)</strong> for media storage. This decouples your storage from the Ghost instance, ensuring uploads are consistent across deployments.</p><p>The main driver behind this, was to use <strong>Ghost</strong> as a completely headless CMS allowing for Gatsby to serve images easily and separately without exposing my Ghost instance to the public. This process allows you to upload images within Ghost and have them available within a Gatsby generated site.</p><p>In this post, I’ll walk through setting up Ghost with <a href="https://github.com/colinmeinke/ghost-storage-adapter-s3" rel="noopener"><code>ghost-storage-adapter-s3</code></a>. Using <strong>Amazon S3 and Amazon CloudFront</strong> to seamlessly serve images  for both Ghost as CMS and Gatsby as a static website.</p><hr><h2 id="why-use-s3-for-ghost-storage">Why Use S3 for Ghost Storage?</h2><ul><li><strong>Scalability</strong>: Media is available no matter how many Ghost instances you run.</li><li><strong>Durability</strong>: S3 handles replication and durability better than local disk.</li><li><strong>Flexibility</strong>: Works with AWS S3 or any S3-compatible service.</li><li><strong>Portability</strong>: Makes running Ghost in containers or serverless setups easier.</li></ul><hr><h2 id="why-use-cloudfront-for-asset-delivery">Why Use CloudFront for asset delivery?</h2><ul><li><strong>Fast</strong>: Caches your content across a global network of edge locations, so users get data from the nearest server instead of your origin. This reduces latency and speeds up page loads.</li><li><strong>Scalability</strong>: Your site can handle sudden traffic spikes without overwhelming your origin server. It distributes requests across multiple edge locations, keeping things stable even under heavy load.</li><li><strong>Security</strong>: integrates with <strong>AWS Shield</strong>, <strong>WAF</strong>, and SSL/TLS to protect against DDoS attacks, bots, and other threats — while ensuring encrypted connections for your users.</li><li><strong>Cost</strong>: By caching content at the edge, CloudFront reduces the amount of data pulled directly from your origin (like S3 or an EC2 instance). That means lower bandwidth costs and more efficient infrastructure usage.</li></ul><hr><h2 id="prerequisites">Prerequisites</h2><ul><li>A running Ghost instance (self-hosted).</li><li>Node.js environment (to install the adapter).</li><li>An Amazon S3 bucket. (Steps to create these will also be detailed below)</li><li>An Amazon CloudFront distribution. (Steps to create these will also be detailed below)</li><li>AWS IAM Access key + Secret key with appropriate permissions. (Steps to create these will also be detailed below)</li></ul><hr><h2 id="step-1-install-ghost-storage-adapter-s3">Step 1: Install <code>ghost-storage-adapter-s3</code></h2><p>Navigate to your Ghost installation directory and install the adapter:</p><pre><code class="language-bash">npm install ghost-storage-adapter-s3
mkdir -p ./content/adapters/storage
cp -r ./node_modules/ghost-storage-adapter-s3 ./content/adapters/storage/s3</code></pre><p>Ghost looks in <code>content/adapters/storage/</code> for storage adapters, so the directory structure is important</p><ul><li>Node.js environment (to install the adapter).</li><li>An S3 bucket (AWS or compatible).</li><li>S3 access key + secret key with appropriate permissions.</li></ul><hr><h2 id="step-2-configure-aws-resources">Step 2: Configure AWS resources</h2><p>There are two ways to proceed with AWS, I would strongly suggest using the CloudFormation template and sticking with Configuration as Source Code, but I have included the manual steps also if that is more preferable. At the end of this s you should have the values for</p><ul><li>AWS CLI Access Key - Used by Ghost to authenticate with AWS</li><li>AWS CLI Secret Access Key - Used in conjunction with the Access Key</li><li>AWS Region - The region in which your resources are deployed</li><li>AWS CloudFront distribution URL - Used as assetHost</li></ul><h3 id="using-a-cloudformation-template">Using a CloudFormation Template</h3><p>I have provided a GitHub repository for this article that offers a bare bones CloudFormation template which will provision the following AWS resources:</p><ul><li>24 Hour CloudFront Cache Policy</li><li>CloudFront Origin Access Control (OAC)</li><li>S3 Storage Bucket</li><li>S3 Storage Bucket Policy - Linking the Distribution/OAC with the bucket</li><li>IAM Ghost User - Used by Ghost to access the bucket securely</li><li>IAM Ghost User Policy</li><li>CloudFront Distribution</li></ul><p>The code can be reviewed here: <a href="https://github.com/atownsend247/brightbot-source-files/blob/main/ghost-storage-adapter-s3/cloudformation-template.yaml">https://github.com/atownsend247/brightbot-source-files/blob/main/ghost-storage-adapter-s3/cloudformation-template.yaml</a></p><p>You can use this to create a new CloudFormation stack from the AWS Console, then update the <code>Project</code> parameter to suit your requirement.</p><p>Finally once created complete, reference the Outputs tab of CloudFormation to get the CloudFront distribution URL to use as&nbsp;<em><strong>assetHost</strong> later on</em>.</p><p>Now the IAM user is created navigate to it in the AWS Console, click on <code>Security Credentials</code> -&gt; <code>Create access key</code>. Select <strong>Command Line Interface (CLI)</strong>, select the confirmation, give it a friendly name and either download the credentials.csv or copy the <strong>Access key</strong> value and <strong>Secret access key</strong> to somewhere safe as we need these later.</p><h3 id="manually-creating-resources">Manually creating resources</h3><p>You'll likely want to configure a separate S3 bucket for your blog, a specific IAM role, and, optionally, CloudFront, to serve from a CDN.</p><h3 id="s3">S3</h3><p><a href="https://github.com/colinmeinke/ghost-storage-adapter-s3#s3"/></p><p>Create a new bucket. The region isn't important at this stage. </p><p>As we are setting this up manually, you can let Amazon CloudFront setup the OAC (Origin Access Control) on your behalf.</p><h3 id="iam">IAM</h3><p><a href="https://github.com/colinmeinke/ghost-storage-adapter-s3#iam"/></p><p>You'll want to create a custom user role in IAM that just gives your Ghost installation the necessary permissions to manipulate objects in its S3 bucket.</p><p>Go to IAM in your AWS console and add a new user. Give it a username specific to your blog, and select&nbsp;<strong>Programmatic access</strong>&nbsp;as the Access type.</p><p>Next, on the permissions page, select&nbsp;<strong>Attach existing policies directly</strong>&nbsp;and click to&nbsp;<strong>Create policy</strong>. For the policy click on the JSON editor and add the following policy. You'll want to replace where it says&nbsp;<strong>your-bucket-name</strong>&nbsp;with the name of your blog's S3 bucket.</p><figure class="kg-card kg-code-card"><pre><code class="language-json">{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::your-bucket-name"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:PutObjectVersionAcl",
                "s3:DeleteObject",
                "s3:PutObjectAcl"
            ],
            "Resource": "arn:aws:s3:::your-bucket-name/*"
        }
    ]
}
</code></pre><figcaption><p><span style="white-space: pre-wrap;">IAM User Policy</span></p></figcaption></figure><p>What this policy does is allow the user access to see the contents of the bucket (the first statement), and then manipulate the objects stored in the bucket (the second statement).</p><p>Finally, create the user and copy the&nbsp;<strong>Access key</strong>&nbsp;and&nbsp;<strong>Secret access key</strong>, these are what you'll use in your configuration.</p><p>At this point your done with the <strong>Ghost</strong> access to Amazon S3</p><h3 id="cloudfront">CloudFront</h3><p><a href="https://github.com/colinmeinke/ghost-storage-adapter-s3#cloudfront"/></p><p>CloudFront is a CDN that replicates objects in servers around the world so your blog's visitors will get your assets faster by using the server closest to them. It uses your S3 bucket as the "source of truth" that it populates its servers with.</p><p>Got to CloudFront in AWS and choose to&nbsp;<strong>Create a Distribution</strong>. On the next screen you'll want to leave everything the same, except change the following:</p><ul><li>Origin - Select Amazon S3<ul><li>Click the <strong>Browse S3</strong> button and find your newly created bucket</li><li>Ensure <strong>Allow private S3 bucket access to CloudFront -&nbsp;<em>Recommended</em><em> is selected as this will setup the secure/private permissions for you</em></strong></li></ul></li><li>Viewer Protocol Policy:&nbsp;<strong>Redirect HTTP to HTTPS</strong></li><li>Compress Objects Automatically:&nbsp;<strong>Yes</strong></li></ul><p>Then create the distribution.</p><p>Finally, in your configuration, use the subdomain for the CloudFront distribution as your setting for&nbsp;<strong><em>assetHost</em></strong>.</p><hr><h2 id="step-3-configure-ghost-to-use-the-adapter">Step 3: Configure Ghost to Use the Adapter</h2><p>In your Ghost config file (<code>config.production.json</code>), add the storage block:</p><pre><code class="language-json">{
  ...,
  "storage": {
    "active": "s3",
    "s3": {
      "accessKeyId": "YOUR_AWS_ACCESS_KEY",
      "secretAccessKey": "YOUR_AWS_SECRET_KEY",
      "region": "your-region",
      "bucket": "your-s3-bucket-name",
      "assetHost": "https://your-cloudfront-endpoint",
      "serverSideEncryption": "AES256",
      "forcePathStyle": true,
      "acl": "private"
    }
  }
}</code></pre><ul><li><code>accessKeyId</code> -&gt; Your AWS access key for the user with required permissions.</li><li><code>secretAccessKey</code> -&gt; Your AWS secret key for the user with required permissions.</li><li><code>region</code> → your S3 bucket’s AWS region.</li><li><code>bucket</code> → the name of your S3 bucket.</li><li><code>assetHost</code> → for serveing media via CloudFront.</li></ul><hr><h2 id="step-4-restart-ghost">Step 4: Restart Ghost</h2><p>Restart your Ghost instance to apply changes:</p><pre><code class="language-bash">ghost restart</code></pre><p>From now on, uploaded images and media will go directly to your S3 bucket instead of local disk.</p><hr><h2 id="step-5-verify-uploads">Step 5: Verify Uploads</h2><ol><li>Log in to your Ghost admin panel.</li><li>Upload an image to a post.</li><li>Check your S3 bucket (or CDN) to confirm the file is there.</li></ol><p>If you see the image in S3 and it loads in your post, you’re good to go.</p><hr><h2 id="conclusion">Conclusion</h2><p>With <strong><code>ghost-storage-adapter-s3</code></strong>, you can decouple Ghost’s media storage from your server, making your blog more resilient and easier to scale. Whether you’re running Ghost in Docker, on Kubernetes, or just want the reliability of cloud storage, this setup is a solid way to go.</p><p>If you also serve your media through a CDN (like CloudFront), you’ll get the added bonus of faster load times for readers all over the world.</p><p>Head over to Part 2 linked below, to expand this further and use a custom domain with AWS CloudFront.</p><hr><h2 id="other-posts-in-this-series">Other posts in this series</h2><ul><li><a href="https://brightbot.co.uk/setting-up-ghost-with-s3-storage-using-ghost-storage-adapter-s3/" rel="noreferrer">Setting Up Ghost with S3 Storage Using ghost-storage-adapter-s3: Part 1</a></li><li><a href="https://brightbot.co.uk/setting-up-ghost-with-s3-storage-using-ghost-storage-adapter-s3-part-2/" rel="noreferrer">Setting Up Ghost with S3 Storage Using ghost-storage-adapter-s3: Part 2</a></li></ul><hr><h2 id="references">References</h2><ul><li><a href="https://ghost.org/">https://ghost.org/</a></li></ul><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://ghost.org/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Ghost: The best open source blog &amp; newsletter platform</div><div class="kg-bookmark-description">Beautiful, modern publishing with email newsletters and paid subscriptions built-in. Used by Platformer, 404Media, Lever News, Tangle, The Browser, and thousands more.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://static.ghost.org/v5.0.0/images/link-icon.svg" alt="Setting Up Ghost with S3 Storage Using ghost-storage-adapter-s3: Part 1"><span class="kg-bookmark-author">Ghost - The Professional Publishing Platform</span></img></div></div><div class="kg-bookmark-thumbnail"><img src="https://ghost.org/images/meta/ghost-6.png" alt="Setting Up Ghost with S3 Storage Using ghost-storage-adapter-s3: Part 1" onerror="this.style.display = 'none'"/></div></a></figure><ul><li><a href="https://github.com/colinmeinke/ghost-storage-adapter-s3">https://github.com/colinmeinke/ghost-storage-adapter-s3</a></li></ul><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/colinmeinke/ghost-storage-adapter-s3"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - colinmeinke/ghost-storage-adapter-s3: An AWS S3 storage adapter for Ghost</div><div class="kg-bookmark-description">An AWS S3 storage adapter for Ghost. Contribute to colinmeinke/ghost-storage-adapter-s3 development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://static.ghost.org/v5.0.0/images/link-icon.svg" alt="Setting Up Ghost with S3 Storage Using ghost-storage-adapter-s3: Part 1"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">colinmeinke</span></img></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/8c0b29dc0b94f6ddbd92e297cfa2f673a64135c86db85769b8238d7b6a5cb0d7/colinmeinke/ghost-storage-adapter-s3" alt="Setting Up Ghost with S3 Storage Using ghost-storage-adapter-s3: Part 1" onerror="this.style.display = 'none'"/></div></a></figure><ul><li><a href="https://github.com/atownsend247/brightbot-source-files/blob/main/ghost-storage-adapter-s3/cloudformation-template.yaml">https://github.com/atownsend247/brightbot-source-files/blob/main/ghost-storage-adapter-s3/cloudformation-template.yaml</a></li></ul><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/atownsend247/brightbot-source-files"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - atownsend247/brightbot-source-files: Provides git access to scripts and templates for associated BrightBot blog posts</div><div class="kg-bookmark-description">Provides git access to scripts and templates for associated BrightBot blog posts - atownsend247/brightbot-source-files</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://static.ghost.org/v5.0.0/images/link-icon.svg" alt="Setting Up Ghost with S3 Storage Using ghost-storage-adapter-s3: Part 1"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">atownsend247</span></img></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/c6e194a6b39459e58442e3c61eb59fb95a6375271b9f8a393342104eb3b26b88/atownsend247/brightbot-source-files" alt="Setting Up Ghost with S3 Storage Using ghost-storage-adapter-s3: Part 1" onerror="this.style.display = 'none'"/></div></a></figure></hr></hr></hr></hr></hr></hr></hr></hr></hr></hr></hr>]]></content:encoded></item><item><title><![CDATA[Linting Documentation with Vale: Consistent, Clear, and Error-Free Writing]]></title><description><![CDATA[If you’ve ever worked on a large documentation project, you know how quickly things can get messy. Different contributors bring different writing styles, terminology gets inconsistent, and small grammar mistakes sneak through. That’s where Vale comes in — a linter for prose that helps keep your docs clean, consistent, and professional.


What is Vale?

Vale is an open-source command-line tool for linting and style-checking text. Think of it like ESLint or Prettier, but for documentation and pros]]></description><link>https://brightbot.co.uk/linting-documentation-with-vale-consistent-clear-and-error-free-writing/</link><guid isPermaLink="false">Ghost__Post__68dd733bcb5315e3604fce56</guid><category><![CDATA[Home Lab]]></category><category><![CDATA[Infrastructure]]></category><dc:creator><![CDATA[Andrew Townsend]]></dc:creator><pubDate>Wed, 01 Oct 2025 18:38:50 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1600267204091-5c1ab8b10c02?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGRvY3VtZW50YXRpb258ZW58MHx8fHwxNzU5MzQzOTE5fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1600267204091-5c1ab8b10c02?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGRvY3VtZW50YXRpb258ZW58MHx8fHwxNzU5MzQzOTE5fDA&ixlib=rb-4.1.0&q=80&w=2000" alt="Linting Documentation with Vale: Consistent, Clear, and Error-Free Writing"/><p>If you’ve ever worked on a large documentation project, you know how quickly things can get messy. Different contributors bring different writing styles, terminology gets inconsistent, and small grammar mistakes sneak through. That’s where <strong>Vale</strong> comes in — a linter for prose that helps keep your docs clean, consistent, and professional.</p><hr><h2 id="what-is-vale">What is Vale?</h2><p><a rel="noopener"><strong>Vale</strong></a> is an open-source command-line tool for linting and style-checking text. Think of it like ESLint or Prettier, but for documentation and prose instead of code. It helps you:</p><ul><li><strong>Enforce a consistent style</strong> across docs.</li><li><strong>Catch grammar, spelling, and clarity issues</strong> early.</li><li><strong>Standardize terminology</strong> (e.g., always use “sign in” instead of “login”).</li><li><strong>Automate reviews</strong> so humans can focus on content, not nitpicks.</li></ul><p>Vale works with Markdown, reStructuredText, AsciiDoc, and more — making it a great fit for docs-as-code workflows.</p><hr><h2 id="why-use-vale">Why Use Vale?</h2><ul><li><strong>Consistency matters</strong>: Readers trust documentation that feels cohesive.</li><li><strong>Saves reviewer time</strong>: Automates style nitpicks so reviewers can focus on accuracy.</li><li><strong>Integrates with CI/CD</strong>: Catch doc issues before merging.</li><li><strong>Customisable</strong>: Define your own style rules or adopt existing ones (like Google or Microsoft style guides).</li></ul><hr><h2 id="installing-vale">Installing Vale</h2><p>Vale is distributed as a single binary — no big dependencies.</p><p>On macOS (via Homebrew):</p><pre><code class="language-bash">brew install vale</code></pre><p>On Linux (via binary release):</p><pre><code class="language-bash">curl -s https://api.github.com/repos/errata-ai/vale/releases/latest \
| grep "browser_download_url.*Linux_64-bit.tar.gz" \
| cut -d '"' -f 4 \
| wget -qi - &amp;&amp; tar -xzf Vale_*_Linux_64-bit.tar.gz</code></pre><p>On Windows: download from the <a href="https://github.com/errata-ai/vale/releases" rel="noopener">Vale releases page</a>.</p><hr><h2 id="setting-up-vale">Setting Up Vale</h2><p>In the root of your docs project, create a <code>.vale.ini</code> file:</p><pre><code class="language-ini">StylesPath = styles
MinAlertLevel = suggestion

Packages = Google, proselint, write-good

[*.md]
BasedOnStyles = Vale, Google, proselint, write-good</code></pre><p>This tells Vale to:</p><ul><li>Look for styles in the <code>styles/</code> directory.</li><li>Use Microsoft’s style guide.</li><li>Apply it to all <code>.md</code> files.</li></ul><hr><h2 id="running-vale">Running Vale</h2><p>Once configured, lint your docs with:</p><pre><code class="language-bash">vale path/to/docs/</code></pre><p>Example output:</p><pre><code class="language-bash">docs/getting-started.md
 25:5  error  'login' should be written as 'sign in'  Google.Terms
 44:10 warning  Avoid using 'simply'                  Google.Avoid</code></pre><p><br/></p><p>Here you can see Vale catching terminology and tone issues automatically.</p><hr><h2 id="customizing-rules">Customizing Rules</h2><p>Vale is powerful because you can define your own style rules. For example, let’s enforce “e-mail” vs “email.”</p><p>Create a file at <code>styles/Custom/Terms.yml</code>:</p><pre><code class="language-yaml">extends: existence
message: "Use 'email' instead of 'e-mail'."
level: error
ignorecase: true
tokens:
  - e-mail</code></pre><p>Now Vale will flag “e-mail” anywhere in your docs.</p><hr><h2 id="using-vale-in-cicd">Using Vale in CI/CD</h2><p>To ensure consistency across teams, add Vale to your build pipeline. For example, in GitHub Actions:</p><pre><code class="language-yaml">name: Lint Docs

on: [pull_request]

jobs:
  vale:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install Vale
        run: |
          curl -fsSL https://install.goreleaser.com/github.com/errata-ai/vale.sh | sh
          sudo mv ./bin/vale /usr/local/bin/
      - name: Run Vale
        run: vale .</code></pre><p>This way, PRs can’t be merged until documentation passes <strong>linting</strong>.</p><hr><h1 id="starter-vale-style-guide">Starter Vale Style Guide</h1><p>This starter style guide enforces <strong>clear, consistent, and professional writing</strong>. It includes rules for terminology, tone, and formatting that you can extend as your docs mature.</p><p>👉 Folder structure example:</p><pre><code class="language-bash">docs/
styles/
  Starter/
    Terms.yml
    Tone.yml
    Spacing.yml
.vale.ini</code></pre><hr><p><code>.vale.ini</code></p><pre><code class="language-ini">StylesPath = styles
MinAlertLevel = suggestion

[*.md]
BasedOnStyles = Starter</code></pre><p>This tells Vale to look in the <code>styles/</code> folder and apply the <code>Starter</code> style to all Markdown files.</p><hr><h2 id="termsyml-%E2%80%93-consistent-terminology"><code>Terms.yml</code> – Consistent Terminology</h2><pre><code class="language-yaml">extends: substitution
message: "Use '%s' instead of '%s'."
level: error
ignorecase: true
swap:
  login: sign in
  log-in: sign in
  e-mail: email
  Github: GitHub
  Javascript: JavaScript</code></pre><p>✅ Enforces consistent word choices and fixes common brand/style mistakes.</p><hr><h2 id="toneyml-%E2%80%93-avoid-filler-words"><code>Tone.yml</code> – Avoid Filler Words</h2><pre><code class="language-yaml">extends: existence
message: "Avoid using '%s'."
level: warning
ignorecase: true
tokens:
  - simply
  - obviously
  - clearly
  - basically
  - just</code></pre><p>✅ Flags words that can sound condescending or unnecessary in documentation.</p><hr><h2 id="spacingyml-%E2%80%93-clean-formatting"><code>Spacing.yml</code> – Clean Formatting</h2><pre><code class="language-yaml">extends: existence
message: "Use a single space between sentences."
level: error
nonword: true
tokens:
  - '\s{2,}'</code></pre><p>✅ Ensures no double spaces sneak into your text.</p><hr><h2 id="how-to-use">How to Use</h2><ol><li>Save the above files into <code>styles/Starter/</code>.</li><li>Update <code>.vale.ini</code> to include <code>Starter</code>.</li><li>Run <code>vale docs/</code> and watch it flag issues.</li></ol><hr><h2 id="example-in-action">Example in Action</h2><p>Input:</p><p><code>To login, just click the button.  Github is</code> simply awesome!<br/></p><p>Output:</p><pre><code class="language-bash"> 1:4  error   Use 'sign in' instead of 'login'.       Starter.Terms
 1:11 warning Avoid using 'just'.                     Starter.Tone
 1:29 error   Use 'GitHub' instead of 'Github'.       Starter.Terms
 1:37 warning Avoid using 'simply'.                   Starter.Tone
 1:50 error   Use a single space between sentences.   Starter.Spacing</code></pre><h2 id="conclusion">Conclusion</h2><p><strong>Vale</strong> takes the guesswork out of documentation quality. By automating style and consistency checks, it frees up writers and reviewers to focus on content and accuracy. Whether you’re maintaining internal runbooks, public APIs, or open-source docs, Vale helps you deliver polished, professional writing every time.</p><p>This starter style guide gives you:</p><ul><li><strong>Terminology enforcement</strong> (consistent words, correct brands).</li><li><strong>Tone checks</strong> (avoid filler or condescending words).</li><li><strong>Formatting rules</strong> (like spacing).</li></ul><p>It’s lightweight but effective — and you can extend it with custom rules as your docs grow.</p><p>If you’re serious about treating docs like code, adding Vale to your toolchain is a no-brainer.</p></hr></hr></hr></hr></hr></hr></hr></hr></hr></hr></hr></hr></hr></hr>]]></content:encoded></item><item><title><![CDATA[Setting Up Network UPS Tools (NUT) for APC UPS Devices on Linux]]></title><description><![CDATA[When you’re running critical systems — whether it’s a home lab, a small business server, or production workloads — power outages can cause real headaches. That’s where an Uninterruptible Power Supply (UPS) comes in. But to make the most of it, your server needs to know when the UPS is running on battery, when it’s low, and when to shut down gracefully.

That’s exactly what Network UPS Tools (NUT) does. In this guide, we’ll walk through setting up NUT on Linux with an APC UPS.


What is NUT?

Net]]></description><link>https://brightbot.co.uk/setting-up-network-ups-tools-nut-for-apc-ups-devices-on-linux/</link><guid isPermaLink="false">Ghost__Post__68dcea1bcb5315e3604fce08</guid><category><![CDATA[Home Lab]]></category><category><![CDATA[Infrastructure]]></category><dc:creator><![CDATA[Andrew Townsend]]></dc:creator><pubDate>Wed, 01 Oct 2025 08:56:05 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1738520420641-0d39da26723c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDM2fHxwb3dlciUyMHN1cHBseXxlbnwwfHx8fDE3NTkzMDkwMTV8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1738520420641-0d39da26723c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMTc3M3wwfDF8c2VhcmNofDM2fHxwb3dlciUyMHN1cHBseXxlbnwwfHx8fDE3NTkzMDkwMTV8MA&ixlib=rb-4.1.0&q=80&w=2000" alt="Setting Up Network UPS Tools (NUT) for APC UPS Devices on Linux"/><p>When you’re running critical systems — whether it’s a home lab, a small business server, or production workloads — power outages can cause real headaches. That’s where an <strong>Uninterruptible Power Supply (UPS)</strong> comes in. But to make the most of it, your server needs to know when the UPS is running on battery, when it’s low, and when to shut down gracefully.</p><p>That’s exactly what <strong>Network UPS Tools (NUT)</strong> does. In this guide, we’ll walk through setting up <strong>NUT</strong> on Linux with an <strong>APC UPS</strong>.</p><hr><h2 id="what-is-nut">What is NUT?</h2><p><strong>Network UPS Tools (NUT)</strong> is an open-source suite that provides:</p><ul><li>Drivers for a wide range of UPS devices (including APC).</li><li>A monitoring daemon to track UPS status.</li><li>Shutdown coordination for multiple systems.</li><li>Remote monitoring over the network.</li></ul><p>It’s highly flexible, works across many UPS models, and is widely used in both enterprise and homelab environments.</p><hr><h2 id="prerequisites">Prerequisites</h2><ul><li>A Linux system (Debian/Ubuntu, RHEL, or similar).</li><li>An APC UPS connected via USB or serial cable.</li><li>Root or sudo access.</li></ul><p>For the purposes of this post, I will be connecting 2x APC UPS-B Back-UPS XS 1400U UPS's to a single Raspberry Pi5</p><hr><h2 id="step-1-install-nut">Step 1: Install NUT</h2><p>On Debian/Ubuntu:</p><pre><code class="language-bash">sudo apt update
sudo apt install nut nut-client nut-server</code></pre><hr><h2 id="step-2-identify-your-ups">Step 2: Identify Your UPS</h2><p>Plug in your APC UPS via USB, then run:</p><pre><code class="language-bash">lsusb
</code></pre><p>You should see something like:</p><pre><code class="language-bash">Bus 001 Device 005: ID 051d:0002 American Power Conversion Uninterruptible Power Supply
Bus 001 Device 004: ID 051d:0002 American Power Conversion Uninterruptible Power Supply</code></pre><p>This tells us the UPS is detected.</p><hr><h2 id="step-3-configure-nut-driver">Step 3: Configure NUT Driver</h2><p>Edit <code>/etc/nut/ups.conf</code>:</p><pre><code class="language-bash"># Generic APC UPS
[apc]
    driver = usbhid-ups
    port = auto
    desc = "APC UPS"

# Specific to my UPSs
[apcupsa]
driver = usbhid-ups
desc = "APC UPS-A Back-UPS XS 1400U 4B2028P50251"
port = auto
vendorid = 051d
productid = 0002
serial = serial1

[apcupsb]
driver = usbhid-ups
desc = "APC UPS-B Back-UPS XS 1400U 4B2028P50042"
port = auto
vendorid = 051d
productid = 0002
serial = serial2</code></pre><ul><li><code>apc</code> is the UPS name (you can choose your own).</li><li><code>usbhid-ups</code> is the driver used for most modern APC UPS devices.</li><li><code>port = auto</code> lets NUT detect the UPS automatically.</li></ul><p>Test the driver:</p><pre><code class="language-bash">sudo upsdrvctl start</code></pre><hr><h2 id="step-4-configure-ups-daemon">Step 4: Configure UPS Daemon</h2><p>Edit <code>/etc/nut/upsd.conf</code>:</p><pre><code class="language-bash">LISTEN 0.0.0.0 3493</code></pre><p>This tells NUT to listen for connections on localhost.</p><p>Then edit <code>/etc/nut/upsd.users</code>:</p><pre><code class="language-bash">[upsmon]
    password = strongpassword
    upsmon primary
    instcmds = ALL</code></pre><hr><h2 id="step-5-configure-nut-client">Step 5: Configure NUT Client</h2><p>Edit <code>/etc/nut/upsmon.conf</code>:</p><pre><code class="language-bash">MONITOR apcupsa@localhost 1 upsmon strongpassword primary
MONITOR apcupsb@localhost 1 upsmon strongpassword primary</code></pre><p>This means:</p><ul><li>Monitor the UPS named <code>apc</code> at <code>localhost</code>.</li><li>Use the <code>admin</code> user with the password.</li><li><code>master</code> indicates this system will manage shutdowns.</li></ul><hr><h2 id="step-6-set-nut-mode">Step 6: Set NUT Mode</h2><p>Edit <code>/etc/nut/nut.conf</code>:</p><pre><code class="language-bash">MODE=netserver</code></pre><p>(Use <code>netserver</code> or <code>netclient</code> if you want multiple systems connected to the UPS.)</p><hr><h2 id="step-7-start-and-enable-services">Step 7: Start and Enable Services</h2><pre><code class="language-bash">sudo systemctl enable nut-server
sudo systemctl enable nut-monitor
sudo systemctl start nut-server
sudo systemctl start nut-monitor</code></pre><hr><h2 id="step-8-verify">Step 8: Verify</h2><p>Check UPS status:</p><pre><code class="language-bash">upsc apc@localhost</code></pre><p>You should see output like:</p><pre><code class="language-bash">battery.charge: 100
battery.charge.low: 10
battery.charge.warning: 50
battery.date: 2001/09/25
battery.mfr.date: 2020/07/10
battery.runtime: 4088
battery.runtime.low: 120
battery.type: PbAc
battery.voltage: 27.3
battery.voltage.nominal: 24.0
device.mfr: American Power Conversion
device.model: Back-UPS XS 1400U
device.serial: myserial1
device.type: ups
driver.name: usbhid-ups
driver.parameter.pollfreq: 30
driver.parameter.pollinterval: 2
driver.parameter.port: auto
driver.parameter.productid: 0002
driver.parameter.serial: myserial1
driver.parameter.synchronous: auto
driver.parameter.vendorid: 051d
driver.version: 2.8.0
driver.version.data: APC HID 0.98
driver.version.internal: 0.47
driver.version.usb: libusb-1.0.26 (API: 0x1000109)
input.sensitivity: medium
input.transfer.high: 280
input.transfer.low: 155
input.transfer.reason: input voltage out of range
input.voltage: 240.0
input.voltage.nominal: 230
ups.beeper.status: enabled
ups.delay.shutdown: 20
ups.firmware: 926.T2 .I
ups.firmware.aux: T2
ups.load: 9
ups.mfr: American Power Conversion
ups.mfr.date: 2020/07/10
ups.model: Back-UPS XS 1400U
ups.productid: 0002
ups.realpower.nominal: 700
ups.serial: myserial1
ups.status: OL
ups.test.result: No test initiated
ups.timer.reboot: 0
ups.timer.shutdown: -1
ups.vendorid: 051d</code></pre><ul><li><code>OL</code> means “On Line” (power is good).</li><li>You’ll see <code>OB</code> (On Battery) if you unplug the UPS.</li></ul><hr><h2 id="step-9-optional-remote-monitoring">Step 9 (Optional): Remote Monitoring</h2><p>If you want multiple servers to shut down gracefully:</p><ol><li>Set <code>MODE=netserver</code> on the UPS host.</li><li>Configure <code>upsd.conf</code> to listen on the LAN IP.</li><li>On client machines, set <code>MODE=netclient</code> and point <code>upsmon.conf</code> at the UPS host.</li></ol><hr><h2 id="conclusion">Conclusion</h2><p>With NUT configured, your Linux system can <strong>monitor your APC UPS, log events, and safely shut down when power runs out</strong>. This setup ensures your data and systems stay protected during outages — and you get peace of mind knowing your UPS isn’t just sitting there, but actively integrated into your infrastructure.</p></hr></hr></hr></hr></hr></hr></hr></hr></hr></hr></hr></hr>]]></content:encoded></item><item><title><![CDATA[Ghost + Gatsby: A Modern Publishing Stack for Fast, Flexible Websites]]></title><description><![CDATA[If you want a publishing platform that’s both powerful and lightning fast, combining Ghost with Gatsby is a great way to go. Ghost handles content management and publishing, while Gatsby builds a highly optimized static site from that content. Together, they deliver the best of both worlds: an easy editor for authors and a blazing-fast frontend for readers.


What is Ghost?

Ghost is a modern, open-source CMS (Content Management System) built specifically for professional publishing. Unlike gene]]></description><link>https://brightbot.co.uk/ghost-gatsby-a-modern-publishing-stack-for-fast-flexible-websites/</link><guid isPermaLink="false">Ghost__Post__68da862bcb5315e3604fcde7</guid><category><![CDATA[Home Lab]]></category><category><![CDATA[Infrastructure]]></category><dc:creator><![CDATA[Andrew Townsend]]></dc:creator><pubDate>Mon, 29 Sep 2025 13:29:30 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1583680599407-f73ab374fff4?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDh8fGdob3N0fGVufDB8fHx8MTc1OTE1MjU5OHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1583680599407-f73ab374fff4?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMTc3M3wwfDF8c2VhcmNofDh8fGdob3N0fGVufDB8fHx8MTc1OTE1MjU5OHww&ixlib=rb-4.1.0&q=80&w=2000" alt="Ghost + Gatsby: A Modern Publishing Stack for Fast, Flexible Websites"/><p>If you want a publishing platform that’s both <strong>powerful</strong> and <strong>lightning fast</strong>, combining <strong>Ghost</strong> with <strong>Gatsby</strong> is a great way to go. Ghost handles content management and publishing, while Gatsby builds a highly optimized static site from that content. Together, they deliver the best of both worlds: an easy editor for authors and a blazing-fast frontend for readers.</p><hr><h2 id="what-is-ghost">What is Ghost?</h2><p><strong>Ghost</strong> is a modern, open-source CMS (Content Management System) built specifically for professional publishing. Unlike general-purpose CMS platforms, Ghost focuses on content creation, monetization, and simplicity.</p><p>Key features include:</p><ul><li><strong>Beautiful, distraction-free editor</strong> (Markdown-based with rich embeds)</li><li><strong>Membership and subscription support</strong></li><li><strong>Built-in SEO and AMP support</strong></li><li><strong>Powerful APIs</strong> for integration (REST and GraphQL)</li><li><strong>Headless CMS mode</strong></li></ul><p>Ghost can be used as a full CMS with its own frontend theme system, or as a <strong>headless CMS</strong> that provides content to another frontend like Gatsby.</p><hr><h2 id="what-is-gatsby">What is Gatsby?</h2><p><strong>Gatsby</strong> is a React-based static site generator. It takes your content (from sources like Ghost, WordPress, Markdown, or APIs) and compiles it into a static site. The result: <strong>super-fast load times, improved SEO, and high scalability</strong>.</p><p>Highlights include:</p><ul><li><strong>Static site generation</strong> with server-side rendering (SSR) and incremental builds</li><li><strong>Rich plugin ecosystem</strong> for data sources and features</li><li><strong>Performance optimization</strong> (image optimization, prefetching, code splitting)</li><li><strong>React-based frontend</strong> for dynamic interactivity</li><li><strong>Deployed anywhere</strong> (Netlify, Vercel, AWS, etc.)</li></ul><hr><h2 id="why-use-ghost-and-gatsby-together">Why Use Ghost and Gatsby Together?</h2><ul><li><strong>Ghost as the content hub</strong> – Editors and authors manage posts in Ghost’s admin panel.</li><li><strong>Gatsby as the frontend</strong> – Pulls content from Ghost’s API and builds a fast, secure, static site.</li><li><strong>Separation of concerns</strong> – Writers focus on content, developers focus on presentation.</li><li><strong>Performance and scalability</strong> – Gatsby sites are static, so they’re extremely fast and easy to host on CDNs.</li><li><strong>Security</strong> – With a static frontend, there’s no exposed CMS backend to attack.</li></ul><hr><h2 id="typical-architecture">Typical Architecture</h2><p>Here’s what a Ghost + Gatsby setup looks like:</p><pre><code class="language-text">   Authors &amp; Editors
          |
          v
   +---------------+
   |     Ghost     |  &lt;-- CMS (Content API)
   | (Headless)    |
   +---------------+
          |
          v
   +---------------+
   |    Gatsby     |  &lt;-- Static site generator (React)
   |   (Builds)    |
   +---------------+
          |
          v
   +---------------+
   |   Deployed    |  &lt;-- CDN / Hosting (Netlify, Vercel, etc.)
   |   Website     |
   +---------------+
          |
          v
       Readers</code></pre><ul><li>Ghost stores and manages your content.</li><li>Gatsby fetches content via the Ghost Content API (or GraphQL).</li><li>Gatsby builds static files (HTML, CSS, JS).</li><li>The site is deployed to a CDN for speed and global availability.</li></ul><hr><h2 id="example-setup">Example Setup</h2><h3 id="install-gatsby-ghost-source-plugin">Install Gatsby Ghost Source Plugin</h3><p>In <code>gatsby-config.js</code>:</p><pre><code class="language-js">module.exports = {
  plugins: [
    {
      resolve: `gatsby-source-ghost`,
      options: {
        apiUrl: `https://your-ghost-site.com`,
        contentApiKey: `YOUR_CONTENT_API_KEY`,
      },
    },
  ],
};</code></pre><h3 id="query-ghost-content-with-graphql">Query Ghost Content with GraphQL</h3><p>Example query in Gatsby:</p><pre><code class="language-graphql">{
  allGhostPost {
    edges {
      node {
        title
        slug
        published_at(formatString: "MMMM DD, YYYY")
        html
      }
    }
  }
}</code></pre><p>This lets you pull posts from Ghost and render them in Gatsby pages.</p><hr><h2 id="benefits-of-this-setup">Benefits of This Setup</h2><ul><li><strong>Best authoring experience</strong> – Ghost provides a clean, intuitive editor.</li><li><strong>Blazing-fast sites</strong> – Gatsby generates static pages optimized for performance.</li><li><strong>Secure &amp; scalable</strong> – The frontend is static, hosted on a CDN.</li><li><strong>Developer-friendly</strong> – Modern React ecosystem for custom UIs.</li><li><strong>Future-proof</strong> – Decoupled CMS architecture for flexibility.</li></ul><hr><h2 id="conclusion">Conclusion</h2><p>Ghost + Gatsby is a modern publishing stack that balances <strong>content creation ease</strong> with <strong>frontend performance and scalability</strong>. Ghost takes care of authors, while Gatsby ensures readers get a super-fast experience.</p><p>If you’re building a blog, magazine, or content-driven business website, this combo is a powerful alternative to traditional CMS platforms.</p><p>In the next part of this series I will talk over fully expanding on Gatsby to generate a full static website, which funnily enough exactly how this site is created.</p></hr></hr></hr></hr></hr></hr></hr>]]></content:encoded></item><item><title><![CDATA[High Availability with HAProxy and Keepalived: A Practical Guide]]></title><description><![CDATA[When it comes to building resilient and scalable infrastructure, two tools often come up together: HAProxy and Keepalived. While HAProxy excels at load balancing and proxying, Keepalived ensures service continuity by managing failover and redundancy. Together, they form a rock-solid foundation for highly available systems.


What is HAProxy?

HAProxy (High Availability Proxy) is an open-source load balancer and reverse proxy widely used in production environments. It sits between clients and bac]]></description><link>https://brightbot.co.uk/high-availability-with-haproxy-and-keepalived/</link><guid isPermaLink="false">Ghost__Post__68da4e58cb5315e3604fcdc3</guid><category><![CDATA[Home Lab]]></category><category><![CDATA[Infrastructure]]></category><dc:creator><![CDATA[Andrew Townsend]]></dc:creator><pubDate>Mon, 29 Sep 2025 09:27:31 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1587620962725-abab7fe55159?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDd8fHByb2dyYW1taW5nfGVufDB8fHx8MTc1OTEzMzM1OXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1587620962725-abab7fe55159?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMTc3M3wwfDF8c2VhcmNofDd8fHByb2dyYW1taW5nfGVufDB8fHx8MTc1OTEzMzM1OXww&ixlib=rb-4.1.0&q=80&w=2000" alt="High Availability with HAProxy and Keepalived: A Practical Guide"/><p>When it comes to building resilient and scalable infrastructure, two tools often come up together: <strong>HAProxy</strong> and <strong>Keepalived</strong>. While HAProxy excels at load balancing and proxying, Keepalived ensures service continuity by managing failover and redundancy. Together, they form a rock-solid foundation for highly available systems.</p><hr><h2 id="what-is-haproxy">What is HAProxy?</h2><p><strong>HAProxy (High Availability Proxy)</strong> is an open-source load balancer and reverse proxy widely used in production environments. It sits between clients and backend servers, distributing traffic efficiently and providing features such as:</p><ul><li><strong>Layer 4 and Layer 7 load balancing</strong> (TCP/HTTP/HTTPS)</li><li><strong>SSL termination</strong></li><li><strong>Health checks</strong> for backend servers</li><li><strong>Sticky sessions</strong></li><li><strong>Advanced routing and ACLs</strong></li><li><strong>Logging and metrics integration</strong></li></ul><p>Because of its performance and flexibility, HAProxy is used by some of the largest web platforms in the world.</p><hr><h2 id="what-is-keepalived">What is Keepalived?</h2><p><strong>Keepalived</strong> is an open-source tool that provides <strong>high availability</strong> and <strong>failover capabilities</strong> for Linux systems. It primarily uses <strong>VRRP (Virtual Router Redundancy Protocol)</strong> to create a floating IP address shared between servers. If the primary node goes down, Keepalived automatically promotes a standby node to take over the virtual IP.</p><p>Key features include:</p><ul><li><strong>VRRP-based failover</strong></li><li><strong>Health checks for services and nodes</strong></li><li><strong>Automatic failover with minimal downtime</strong></li><li><strong>Lightweight and easy to configure</strong></li></ul><hr><h2 id="why-use-haproxy-and-keepalived-together">Why Use HAProxy and Keepalived Together?</h2><p>Using HAProxy alone gives you load balancing across your backend servers. But what happens if the HAProxy server itself fails? That’s where Keepalived comes in.</p><p>With <strong>HAProxy + Keepalived</strong>, you get:</p><ol><li><strong>Load balancing and traffic management</strong> (via HAProxy).</li><li><strong>High availability of the load balancer itself</strong> (via Keepalived).</li><li><strong>A floating virtual IP</strong> that clients connect to, which always points to the active HAProxy node.</li><li><strong>Failover with minimal service disruption</strong> when a node fails.</li></ol><p>This setup prevents a single point of failure in your load balancing layer.</p><hr><h2 id="typical-architecture">Typical Architecture</h2><p>Here’s a simplified setup:</p><pre><code class="language-text">      +---------------------+
Client ---&gt; VIP:80/443 (Managed by Keepalived)
                 |
     -----------------------------
     |                           |
+------------+             +------------+
| HAProxy #1 |             | HAProxy #2 |
| (MASTER)   |             | (BACKUP)   |
| 10.1.0.10  |             | 10.2.0.10  |
+------------+             +------------+
     |                           |
     |       Load Balancing      |
     -----------------------------
     |       |       |       |
+---------+ +---------+ +---------+
| Backend | | Backend | | Backend |
| Server1 | | Server2 | | Server3 |
+---------+ +---------+ +---------+</code></pre><p/><ul><li>Clients connect to a <strong>virtual IP (VIP)</strong> managed by Keepalived.</li><li>HAProxy instances listen on this VIP and distribute traffic to backend servers.</li><li>If the master HAProxy node fails, Keepalived promotes the backup node and reassigns the VIP instantly.</li></ul><hr><h2 id="practical-guide">Practical guide</h2><p>This guide is written from the perspective of using a Debian 12 based OS running <code>haproxy/bookworm-backports-2.8,now 2.8.15-1~bpo12+1 amd64</code> and <code>keepalived/oldstable,now 1:2.2.7-1+b2 amd64</code> but should still be applicable for any other distribution using similar tooling.</p><h2 id="setting-up-keepalived">Setting up Keepalived</h2><h3 id="install">Install</h3><p>Ensure that Keepalived is installed on both the primary and backup servers. You can install it using your system's package manager:</p><p>For Debian/Ubuntu:</p><pre><code class="language-bash">sudo apt install keepalived -y</code></pre><h3 id="primary-server">Primary server</h3><p>Once installed edit the Keepalived configuration file on the primary server. The default configuration file is usually located at&nbsp;<code>/etc/keepalived/keepalived.conf</code>. Use your preferred text editor to open the file:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml"># /etc/keepalived/keepalived.conf
global_defs {
    enable_script_security
}

vrrp_script check-script {
   script "/usr/bin/pgrep haproxy"
   interval 2
   user root
}

vrrp_instance haproxy-vip-lc0-haproxy-01 {
    state MASTER
    priority 101
    interface ens18
    virtual_router_id 10

    advert_int 1

    unicast_src_ip 10.1.0.10 dev ens18
    unicast_peer {
        10.2.0.10
    }

    authentication {
        auth_type PASS
        auth_pass vagrant
    }

    virtual_ipaddress {
        10.0.0.100 dev ens18
    }
    track_script {
       check-script
    }
}</code></pre><figcaption><p><span style="white-space: pre-wrap;">Primary server: /etc/keepalived/keepalived.conf</span></p></figcaption></figure><p>Note for the primary server, the state should be <code>MASTER</code> and priority higher than the secondary server.</p><h3 id="secondary-servers">Secondary Server(s)</h3><figure class="kg-card kg-code-card"><pre><code class="language-yaml"># /etc/keepalived/keepalived.conf
global_defs {
    enable_script_security
}

vrrp_script check-script {
   script "/usr/bin/pgrep haproxy"
   interval 2
   user root
}

vrrp_instance haproxy-vip-lc0-haproxy-02 {
    state BACKUP
    priority 100
    interface ens18
    virtual_router_id 10

    advert_int 1

    unicast_src_ip 10.2.0.10 dev ens18
    unicast_peer {
        10.1.0.10
    }

    authentication {
        auth_type PASS
        auth_pass vagrant
    }

    virtual_ipaddress {
        10.0.0.100 dev ens18
    }
    track_script {
       check-script
    }
}</code></pre><figcaption><p><span style="white-space: pre-wrap;">Secondary server: /etc/keepalived/keepalived.conf</span></p></figcaption></figure><p>Note for the secondary server, the state should be <code>BACKUP</code> and priority lower than the primary server.</p><hr><h2 id="setting-up-haproxy">Setting up HAProxy</h2><p>Installing HAProxy requires a bit of apt repository configuration to get the desired HAProxy 2.8 the Debian site gives a handy helper page which gives instructions for selecting your target OS and version for specific HA Proxy versions, this can be found here: <a href="https://haproxy.debian.net/#distribution=Debian&amp;release=bookworm&amp;version=2.8">https://haproxy.debian.net/#distribution=Debian&amp;release=bookworm&amp;version=2.8</a></p><p>Once the steps are followed HAProxy 2.8 can be installed as follows</p><pre><code class="language-bash"># apt-get update
# apt-get install haproxy=2.8.\*</code></pre><p>Once installed you can go ahead and edit the HAProxy config file <code>/etc/haproxy/haproxy.cfg</code></p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">global
	log /dev/log	local0
	log /dev/log	local1 notice
	chroot /var/lib/haproxy
	stats socket /run/haproxy/admin.sock mode 660 level admin
	stats timeout 30s
	user haproxy
	group haproxy
	daemon

	lua-load /etc/haproxy/cors.lua

	# Default SSL material locations
	ca-base /etc/ssl/certs
	crt-base /etc/ssl/private

	# See: https://ssl-config.mozilla.org/#server=haproxy&amp;server-version=2.0.3&amp;config=intermediate
        ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
        ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
        ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets

defaults
	log	global
	mode	http
	option	httplog
	option	dontlognull
        timeout connect 5000
        timeout client  50000
        timeout server  50000
	errorfile 400 /etc/haproxy/errors/400.http
	errorfile 403 /etc/haproxy/errors/403.http
	errorfile 408 /etc/haproxy/errors/408.http
	errorfile 500 /etc/haproxy/errors/500.http
	errorfile 502 /etc/haproxy/errors/502.http
	errorfile 503 /etc/haproxy/errors/503.http
	errorfile 504 /etc/haproxy/errors/504.http

listen stats
        bind    10.1.0.10:9000
        mode    http
        
        stats   enable
        stats   hide-version
        stats   uri       /
        stats   refresh   30s

frontend web
    bind :80

    acl frontend-ghost hdr_sub(host) -i ghost.atathome.me

    use_backend backend-ghost if frontend-ghost

backend backend-ghost
    server lc0-ghost 10.0.0.1:80 check
    timeout server 30s
</code></pre><figcaption><p><span style="white-space: pre-wrap;">/etc/haproxy/haproxy.cfg</span></p></figcaption></figure><p>This is pretty much the out of the box config with an addition of a sample backend, this should be changed on both the primary and secondary server. Note the <code>listen stats</code> bind address should not be the VIP and be the management IP.</p><hr><h3 id="test-and-verify">Test and Verify</h3><p>After configuring Keepalived on both servers, test the configuration for syntax errors:</p><pre><code class="language-bash">sudo keepalived -t</code></pre><p>If there are no errors, start Keepalived on both servers:</p><pre><code class="language-bash">sudo systemctl start keepalived</code></pre><p>You can verify the high availability setup by checking the VIP address. On the primary server, the VIP should be active. On the backup server, it should be in a backup state. You can use the following command to check the status:</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">~# ip a
1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: ens18: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether bc:24:11:e1:08:40 brd ff:ff:ff:ff:ff:ff
    altname enp0s18
    inet 10.1.0.10/22 brd 10.1.0.255 scope global ens18
       valid_lft forever preferred_lft forever
    inet 10.0.0.100/32 scope global ens18
       valid_lft forever preferred_lft forever</code></pre><figcaption><p dir="ltr"><code spellcheck="false" style="white-space: pre-wrap;"><span>ip a</span></code><span style="white-space: pre-wrap;"> output logs</span></p></figcaption></figure><p>You should see the VIP address (<code>10.0.0.100</code>&nbsp;in this example) on the active server and not on the standby server.</p><p>Keepalived will automatically manage the failover process. If the primary server goes down or HAProxy becomes unresponsive, the VIP will migrate to the backup server.</p><p>If the primary server has not attached the VIP you can debug errors using journal</p><figure class="kg-card kg-code-card"><pre><code class="language-bash"># journalctl -xefu keepalived
Sep 29 10:35:37 lc0-haproxy-01 Keepalived_vrrp[55583]: Script `check-script` now returning 0
Sep 29 10:35:37 lc0-haproxy-01 Keepalived_vrrp[55583]: VRRP_Script(check-script) succeeded
Sep 29 10:35:37 lc0-haproxy-01 Keepalived_vrrp[55583]: (haproxy-vip-lc0-haproxy-01) Entering BACKUP STATE
Sep 29 10:35:37 lc0-haproxy-01 Keepalived_vrrp[55583]: (haproxy-vip-lc0-haproxy-01) received lower priority (100) advert from 10.2.0.10 - discarding
Sep 29 10:35:38 lc0-haproxy-01 Keepalived_vrrp[55583]: (haproxy-vip-lc0-haproxy-01) received lower priority (100) advert from 10.2.0.10 - discarding
Sep 29 10:35:39 lc0-haproxy-01 Keepalived_vrrp[55583]: (haproxy-vip-lc0-haproxy-01) received lower priority (100) advert from 10.2.0.10 - discarding
Sep 29 10:35:40 lc0-haproxy-01 Keepalived_vrrp[55583]: (haproxy-vip-lc0-haproxy-01) Entering MASTER STATE</code></pre><figcaption><p dir="ltr"><span style="white-space: pre-wrap;">Journal logs for keepalived</span></p></figcaption></figure><p>When successful you should see the message <code>Entering MASTER STATE</code></p><p/><hr><h2 id="benefits-of-this-setup">Benefits of This Setup</h2><ul><li><strong>Fault tolerance</strong> – no single point of failure at the load balancer layer.</li><li><strong>Scalability</strong> – distribute traffic across multiple backend servers.</li><li><strong>Minimal downtime</strong> – failover happens automatically and quickly.</li><li><strong>Flexibility</strong> – works with web apps, APIs, databases, and more.</li></ul><hr><h2 id="conclusion">Conclusion</h2><p>By combining <strong>HAProxy</strong> and <strong>Keepalived</strong>, you can build a highly available, fault-tolerant, and scalable load balancing infrastructure. HAProxy ensures smart traffic distribution, while Keepalived keeps the load balancer itself redundant and resilient.</p><p>Whether you’re running a high-traffic website, an API service, or a critical enterprise application, this duo is a tried-and-true solution for uptime and reliability.</p></hr></hr></hr></hr></hr></hr></hr></hr></hr>]]></content:encoded></item></channel></rss>