<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="pretty-atom-feed.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>blog.chobble.com</title>
  <subtitle>Nix/NixOS, self hosting, and owning your data</subtitle>
  <link href="https://blog.chobble.com/feed/feed.xml" rel="self" />
  <link href="https://blog.chobble.com/" />
  <updated>2026-03-30T00:00:00Z</updated>
  <id>https://blog.chobble.com/</id>
  <author>
    <name>Chobble</name>
  </author>
  <entry>
    <title>Updates to Chobble Tickets</title>
    <link href="https://blog.chobble.com/blog/26-03-30-one-month-of-chobble-tickets/" />
    <updated>2026-03-30T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/26-03-30-one-month-of-chobble-tickets/</id>
    <content type="html">&lt;p&gt;One month-ish ago I announced &lt;a href=&quot;https://tickets.chobble.com&quot;&gt;Chobble Tickets&lt;/a&gt;, an open source ticket selling platform I made to host on Bunny&#39;s Edge Scripts at very low cost (or I can host for you).&lt;/p&gt;
&lt;p&gt;I&#39;ve been adding features since, partly based on feedback from the ten or so customers that have signed up so far, and partly based on my ideas for what the platonic ideal of a &amp;quot;fully free and unencumbered&amp;quot; ticket sales platform would look like.&lt;/p&gt;
&lt;p&gt;Much of this work has been along the lines of making the system easier for others to host and build on. I made a &amp;quot;builder&amp;quot; interface that can launch new sites, so the system is now self-replicating. I rejigged the encryption so it&#39;s more performant, and easier to customise. I&#39;ve refactored tonnes of endpoints and routing code to distill key paths to a few solid patterns.&lt;/p&gt;
&lt;p&gt;And I&#39;ve made much more of the system customisable and programmable too. Users can now customise their:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Google/Apple Wallet pass signing info&lt;/li&gt;
&lt;li&gt;Email provider&lt;/li&gt;
&lt;li&gt;Domain name &amp;amp; custom domain name&lt;/li&gt;
&lt;li&gt;Booking questions&lt;/li&gt;
&lt;li&gt;Booking fee&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And they can enable a public API or create keys for a private one. I read &lt;a href=&quot;https://www.inkandswitch.com/essay/malleable-software/&quot;&gt;this article about malleable software&lt;/a&gt; which has inspired me to keep pushing towards making Tickets more tool-like and flexible, and to give as much control to users as I can and to trust them with it.&lt;/p&gt;
&lt;p&gt;I also created a (work in progress) site at &lt;a href=&quot;https://tickets.chobble.com&quot;&gt;tickets.chobble.com&lt;/a&gt; which describes the system and gives it an honest comparison to many of the other options out there. I don&#39;t want to try and persuade anyone to use Chobble Tickets if there&#39;s a better fit for them out there (either open source or otherwise).&lt;/p&gt;
&lt;p&gt;The feedback from my early users has been great - users of varying technical ability have found it easy to do the things they need, and I have already saved some users hundreds of pounds versus paying one of the big platforms like Eventbrite.&lt;/p&gt;
&lt;p&gt;If you do ticketed events and you&#39;d like to try a new, open source, simple, very low cost option, check out Chobble Tickets with the links below.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/chobbledotcom/tickets/&quot;&gt;Github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tickets.chobble.com&quot;&gt;Details on my site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tix.chobble.com/ticket/register&quot;&gt;Sign up for hosting with me&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Chobble Tickets</title>
    <link href="https://blog.chobble.com/blog/26-02-27-chobble-tickets/" />
    <updated>2026-02-27T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/26-02-27-chobble-tickets/</id>
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://tickets.chobble.com&quot;&gt;Chobble Tickets&lt;/a&gt; is an open source ticket selling platform I made (and can host for you).&lt;/p&gt;
&lt;p&gt;The ticket-selling world is one of rentier capitalism. The sales platforms add their own fees onto every sale, on top of the unavoidable payment processing fees. At a minimum, this works out to about 70p of inescapable costs on every sale.&lt;/p&gt;
&lt;p&gt;This seems like total bullshit to me! The actual computational and storage costs resulting from that sale probably never amount to more than 2p. It doesn&#39;t really make much odds to the sales platform whether you make 1 sale or 1,000 - you only ever cost them any measurable amount of money when you take up a human&#39;s paid time.&lt;/p&gt;
&lt;p&gt;Chobble Tickets has been designed to be hosted on &amp;quot;edge&amp;quot; scripts - Bunny CDN&#39;s, specifically. These are compiled single-file JavaScript sites which are only active when processing a request, and which scale up and down (to zero hosts!) over Bunny&#39;s CDN, and which can connect to databases which also scale up and down automatically.&lt;/p&gt;
&lt;p&gt;All of that means that it&#39;s a great fit for building an app which:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Doesn&#39;t cost anything when it&#39;s not in use&lt;/li&gt;
&lt;li&gt;Can deal with huge surges in traffic&lt;/li&gt;
&lt;li&gt;Responds quickly to everyone on the planet&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pretty cool stuff! And a great fit for ticket sales which come in bursts.&lt;/p&gt;
&lt;p&gt;I&#39;m selling hosting for &lt;strong&gt;£50/year&lt;/strong&gt; with no cut of your ticket sales, which should be more than enough to cover the actual costs to me but still much cheaper than any other option out there.&lt;/p&gt;
&lt;p&gt;Support (beyond bug fixes) and customisation will be paid, and I hope I&#39;ll earn my income by tweaking it or its email templates for bigger clients.&lt;/p&gt;
&lt;p&gt;The system already has a bunch of neat features like QR code check-ins, a public-facing site, email receipts, webhooks, iframe embeds, event groups,recurring events, holidays, custom branding.. And more I&#39;ve forgotten.&lt;/p&gt;
&lt;p&gt;Here are all the relevant links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/chobbledotcom/tickets/&quot;&gt;Github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tickets.chobble.com&quot;&gt;Details on my site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tix.chobble.com/ticket/register&quot;&gt;Sign up for hosting with me&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>The BYD Dolphin&#39;s FM tuner is in the &quot;Media Center&quot; app, not the &quot;DAB+&quot; radio one</title>
    <link href="https://blog.chobble.com/blog/25-10-22-byd-dolphin-fm-radio/" />
    <updated>2025-10-22T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/25-10-22-byd-dolphin-fm-radio/</id>
    <content type="html">&lt;p&gt;The BYD Dolphin electric car has a DAB+ and an FM/AM radio, but the FM radio is not in the &amp;quot;DAB+&amp;quot; app on the infotainment system - it&#39;s in the &amp;quot;Media Center&amp;quot; one.&lt;/p&gt;
&lt;p&gt;This might seem like a pretty odd and specific thing to mention on this blog but it seems like this fact is not readily available elsewhere on the internet.&lt;/p&gt;
&lt;p&gt;The other day I spent a frustrating few minutes poking around in the Settings app, and the DAB+ app, trying to figure out how to get the FM tuner working - which the BYD website says exists on all Dolphin models.&lt;/p&gt;
&lt;p&gt;So maybe me mentioning the fact that the BYD Dolphin&#39;s FM radio is in the &amp;quot;Media Centre&amp;quot; app, which you&#39;d normally only ever open if you wanted to play multimedia files from a USB stick, will save someone else from accidentally pressing the re-scan button on the DAB+ radio - which takes ages.&lt;/p&gt;
&lt;p&gt;The analog radio tuner comes with its own favourites system and the audio quality seems decent from my tests. And now that I know how to find the FM/AM radio in the BYD interface, I can use it when the signal is too flaky for DAB or when I want to listen.&lt;/p&gt;
&lt;p&gt;If you only remember one thing today, make it this: &lt;strong&gt;If you&#39;re looking for the FM radio on your BYD Dolphin&#39;s infotainment / dashboard - it&#39;s in the &amp;quot;Media Center&amp;quot; app.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Addendum: The car is overall really good and great value for money, and while it has some quirks like the FM radio not being in the same place as the DAB+ one in the interface, or the lane-assist or daytime running light preferences not sticking, it&#39;s probably a reasonably average amount of quirks and none are that painful.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>The Guardian should incorporate climate science into its &quot;Food&quot; section</title>
    <link href="https://blog.chobble.com/blog/25-11-11-one-year-of-chobble/" />
    <updated>2025-09-08T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/25-11-11-one-year-of-chobble/</id>
    <content type="html">&lt;p&gt;Like many British lefties, I check The Guardian website pretty much daily. And I am regularly disappointed by how heavily meat and dairy feature on their homepage and in their &amp;quot;Food&amp;quot; section without any mention of their impact on climate change.&lt;/p&gt;
&lt;p&gt;It&#39;s especially jarring because of The Guardian&#39;s repeated pledges to tell the truth when it comes to the facts of anthropogenic climate change. They make a big song and dance about covering the climate accurately - and they mostly do a good job - so articles about how to make the perfect beef stroganoff or lamb kofta really stick out like sore thumbs when they&#39;re presented without any context about the environmental damage caused by making those meals part of your diet.&lt;/p&gt;
&lt;p&gt;I wrote to The Guardian&#39;s letters page about this a while back, and I never heard anything back - so today I started &lt;a href=&quot;https://www.change.org/GuardianFood&quot;&gt;this petition asking The Guardian to incorporate climate science into its food section&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I know that Change.org petitions generally have limited success, but you never know. And I&#39;m slightly biased towards optimism there too, after a petition I years ago &lt;a href=&quot;https://www.change.org/p/make-guinness-vegetarian-vegan-friendly/&quot;&gt;asking Guinness to go vegan&lt;/a&gt; seemed to work - they changed their recipe and &lt;a href=&quot;https://youtube.com/watch?v=HQlQA67HQhk&quot;&gt;Stephen Colbert shouted out my petition as the reason&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;So on the off chance you&#39;re reading this and are similarly annoyed by The Guardian&#39;s food sections, please &lt;strong&gt;&lt;a href=&quot;https://www.change.org/GuardianFood&quot;&gt;sign the petition&lt;/a&gt;&lt;/strong&gt; and keep your fingers crossed!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>My Patreon didn&#39;t work out and is now free</title>
    <link href="https://blog.chobble.com/blog/25-08-10-making-patreon-free/" />
    <updated>2025-08-10T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/25-08-10-making-patreon-free/</id>
    <content type="html">&lt;p&gt;Back in January &lt;a href=&quot;https://blog.chobble.com/blog/25-01-13-patreon-group/&quot;&gt;I started a Patreon page for online marketing advice&lt;/a&gt;. I wanted to take the &amp;quot;teach a man to fish&amp;quot; approach to search engine optimisation - to demystify it and put business owners in control of their own websites and online presences.&lt;/p&gt;
&lt;p&gt;The feedback I got from subscribers was really good - people who implemented my advice saw their websites climb through the Google rankings and attract more visitors, and they said I explained things in ways that non-nerdy types could understand. But it was really, really hard to get people to join.&lt;/p&gt;
&lt;p&gt;People would message me wanting SEO help, willing to spend thousands of pounds with me if I was able to bump them up through the Google rankings even slightly. I would point them to my Patreon, explain that everything they needed to know was there for £20 a month, and that I&#39;d be happy to answer any questions they had. But almost none of those people joined.&lt;/p&gt;
&lt;p&gt;Creating posts and videos for Patreon was time-consuming and exhausting work. Recording and editing videos took ages, and more than a few times I had to scrap &amp;quot;near-complete&amp;quot; videos because my microphone glitched out, or the background noise was too high, or I hadn&#39;t taken an antihistamine and you could hear me sniffling.&lt;/p&gt;
&lt;p&gt;Engagement levels on the Patreon page were near to zero, most of the time. I had hoped that I&#39;d be able to create a little community of small business owners helping each other out and giving me feedback on my videos, but most videos had no comments or even &amp;quot;likes&amp;quot;. I wondered whether anyone even saw them - Patreon&#39;s stats would tell me that videos I&#39;d spent all day making had never been watched or had a total view time of two seconds, even months later. I started to hope that Patreon&#39;s stats were just busted - because if they were right, I was often talking to nobody.&lt;/p&gt;
&lt;p&gt;And even though I didn&#39;t &amp;quot;need&amp;quot; the money, it was hard to avoid doing the mental maths of how much I was earning &amp;quot;per hour&amp;quot; when working on my Patreon. After six months I&#39;d managed to get up to about £150 per month, after Patreon&#39;s 12% cut. Each video would take me at least four hours to make. I made at least three posts each month. So that&#39;s a minimum of 12 hours of work per month for £150 before tax. Slightly above the minimum wage.&lt;/p&gt;
&lt;p&gt;All in all, it was just very disheartening. I felt like the &amp;quot;content&amp;quot; I was creating was pretty good - not super slick, but honest and useful - but nobody was seeing it or engaging with it, and it was locked behind a paywall I couldn&#39;t convince people to climb over.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So: I have now made my Patreon entirely &amp;quot;free&amp;quot;, and have removed all of the membership tiers. I have re-uploaded all of its videos to &lt;a href=&quot;https://www.youtube.com/@ChobbleDotCom&quot;&gt;YouTube on my @ChobbleDotCom channel&lt;/a&gt;, as well as to my own website on the new &lt;a href=&quot;https://chobble.com/videos/&quot;&gt;&amp;quot;videos&amp;quot; section&lt;/a&gt;. I&#39;ve copied all of my non-video text guides over to a new &lt;a href=&quot;https://chobble.com/guides/&quot;&gt;&amp;quot;guides&amp;quot; section&lt;/a&gt; too.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you&#39;d like to learn about search engine optimisation, content writing, the effective use of AI, tweaking your browser setup, tidying your website, tracking search results, or a bunch more stuff - check out those links.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hopefully by making all my content free and readily available, it will provide more value than it was doing behind Patreon. It&#39;s likely that without the (small) financial incentive, I will produce videos and guides at a slower pace. But that&#39;s fine - people can always &lt;a href=&quot;https://chobble.com/contact/&quot;&gt;hire me&lt;/a&gt; if they want me to explain / write something specifically for them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you previously subscribed for my Patreon page, thank you.&lt;/strong&gt; If you feel like your payments were wasted now that the content is free, please get in touch with me. I&#39;ll do whatever I can to keep you happy - including refunding you (minus Patreon&#39;s fees) if you really want. But I&#39;d prefer to just give you some personalised SEO advice or something.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you&#39;re considering making a living off Patreon, good luck!&lt;/strong&gt; You will need to spend a lot more time doing self-promotion than I did. But you might want to check out &lt;a href=&quot;https://ghost.org&quot;&gt;Ghost&lt;/a&gt; instead because Patreon&#39;s commission will start to sting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you still want to support my efforts, wow that&#39;s very cool of you!&lt;/strong&gt; You can do so on &lt;a href=&quot;https://en.liberapay.com/chobble/&quot;&gt;LiberaPay&lt;/a&gt; or &lt;a href=&quot;https://github.com/chobbledotcom/&quot;&gt;Github sponsors&lt;/a&gt;. &amp;lt;3&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Play-Test - An open-source app for storing BS 14960 inspections of inflatable play equipment</title>
    <link href="https://blog.chobble.com/blog/25-07-20-play-test-bs-14960/" />
    <updated>2025-07-20T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/25-07-20-play-test-bs-14960/</id>
    <content type="html">&lt;p&gt;I worked in the bouncy castle world as a software dev for 10 years before I joined Bandcamp so a lot of the work I&#39;ve done as a freelancer has been with the companies that I had already had relationships with from that industry.&lt;/p&gt;
&lt;p&gt;Back in May I translated a C# desktop app for storing &#39;Portable Appliance Tests&#39; that fellow industry nerd Spencer Elliott had built into a Rails webapp, named &lt;code&gt;patlog&lt;/code&gt; (&lt;a href=&quot;https://blog.chobble.com/blog/25-05-02-patlog/&quot;&gt;/blog/25-05-02-patlog/&lt;/a&gt;). Our combined skills worked well - Spencer did the hard work of figuring out all of the database columns and validations for each field, and I built on that base to make a simple webapp with a few quality-of-life additions like user accounts and QR codes.&lt;/p&gt;
&lt;p&gt;Shortly after I released &lt;code&gt;patlog&lt;/code&gt;, Spencer released another C# app under the MIT license - this time for storing bouncy castle inspections and creating PDF reports from them - as Spencer had recently trained as an inspector and found that there was no open source way to create pretty inspection reports and to store past test details to make re-tests faster.&lt;/p&gt;
&lt;p&gt;There IS a system for logging these inspections and displaying them online - it&#39;s a scheme called PIPA (&lt;a href=&quot;https://www.pipa.org.uk&quot;&gt;pipa.org.uk&lt;/a&gt;), and I actually built an earlier version of their website / database for them about nine years ago - which was a translation of an Access database in classic ASP to a more modern C# ASP.Net webapp. But the PIPA database is not open source - inspectors / other inspection bodies cannot host their own version of it, and cannot modify the way it works.&lt;/p&gt;
&lt;p&gt;So, over the last six weeks-ish, I have been working on a new Ruby on Rails app, &lt;code&gt;Play-Test&lt;/code&gt; (&lt;a href=&quot;https://play-test.co.uk&quot;&gt;play-test.co.uk&lt;/a&gt;), which again translates Spencer&#39;s inspection storage code. It includes a whole bunch of nice features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;QR Codes&lt;/li&gt;
&lt;li&gt;PDF Generations&lt;/li&gt;
&lt;li&gt;Nice form fields with comments&lt;/li&gt;
&lt;li&gt;Company logo and signature uploads&lt;/li&gt;
&lt;li&gt;Seed data for testing&lt;/li&gt;
&lt;li&gt;Loads of rspec / Capybara tests&lt;/li&gt;
&lt;li&gt;&amp;quot;Dirty forms&amp;quot; code&lt;/li&gt;
&lt;li&gt;Pre-filling fields from previous records&lt;/li&gt;
&lt;li&gt;Heavy i18n toml usage to support translations&lt;/li&gt;
&lt;li&gt;Docker container&lt;/li&gt;
&lt;li&gt;Github actions to push to Dockerhub, scan for vulnerabilities, etc&lt;/li&gt;
&lt;li&gt;Excellent domain name: play-test.co.uk&lt;/li&gt;
&lt;li&gt;Copyleft AGPLv3 license&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I tried to use a very simple tech stack that will be easy for contributors to get up to speed with. I used Claude Code a lot - with plenty of oversight, which sped up a lot of the boilerplate type code - although I&#39;ve tried to minimise that too by using helper methods and keeping things reasonably DRY.&lt;/p&gt;
&lt;p&gt;I&#39;ve built it to be very flexible, because once it&#39;s stable for BS EN 14960 records I can well imagine users wanting to log inspections for other types of equipment not covered by 14960, like inflatable pillows, or even for other types of regular tests entirely.&lt;/p&gt;
&lt;p&gt;A few alpha testers have been using the app and given me some great feedback and bug reports, so I already have a bit of a TODO list building up.&lt;/p&gt;
&lt;p&gt;The code is on Github &lt;a href=&quot;https://github.com/chobble-mirror/play-test/&quot;&gt;here&lt;/a&gt; and mirrored to my Forgejo forge &lt;a href=&quot;https://git.chobble.com/chobble/play-test/&quot;&gt;here&lt;/a&gt;. I was hoping to host everything on my Forgejo first, but I&#39;ve had a hard time getting actions to behave reliably, but they&#39;re always 100% on Github. I might try again in the future.&lt;/p&gt;
&lt;p&gt;In terms of hosting - you can host it yourself using Docker or however else you fancy, or you can contact me to pay for hosting on my official instance at &lt;a href=&quot;https://play-test.co.uk&quot;&gt;play-test.co.uk&lt;/a&gt;. Pricing is subject to change, but at the moment I&#39;m asking for £5 a month to store unlimited tests - with one image per test. You can also contact me to arrange hosting an entire instance for you with admin access, if you&#39;re from a testing body perhaps?&lt;/p&gt;
&lt;p&gt;So if you happen to be a involved in the bouncy castle inspection world, and you&#39;re looking for an open source way to store your records, check out Play-Test at &lt;a href=&quot;https://play-test.co.uk&quot;&gt;play-test.co.uk&lt;/a&gt;!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>patlog.co.uk - A free place to log PAT inspections</title>
    <link href="https://blog.chobble.com/blog/25-05-02-patlog/" />
    <updated>2025-05-02T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/25-05-02-patlog/</id>
    <content type="html">&lt;div class=&quot;video-container&quot;&gt;
  &lt;iframe src=&quot;https://iframe.mediadelivery.net/embed/417100/da1828cc-d387-482a-9875-6511fd2acf22?autoplay=false&amp;loop=false&amp;muted=false&amp;preload=true&amp;responsive=true&quot; loading=&quot;lazy&quot; allow=&quot;accelerometer;gyroscope;autoplay;encrypted-media;picture-in-picture;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;I had some free time this week - between working on &lt;a href=&quot;https://blog.chobble.com/blog/25-01-14-libregig/&quot;&gt;Libregig&lt;/a&gt;, the &lt;a href=&quot;https://blog.chobble.com/blog/25-03-28-chobble-template/&quot;&gt;Chobble Template&lt;/a&gt;, and my &lt;a href=&quot;https://blog.chobble.com/blog/25-01-13-patreon-group/&quot;&gt;Patreon&lt;/a&gt; - and was looking for something to do, when I saw that a fellow nerd in the bouncy castle-adjacent world, Spencer, had written a &lt;a href=&quot;https://github.com/Spencer-reborn-Elliott/PAT-Inspection-Logger&quot;&gt;PAT Inspection Logger&lt;/a&gt; desktop app.&lt;/p&gt;
&lt;p&gt;Portable Appliance Testing (PAT) is a process for testing electrical items, and despite &lt;a href=&quot;https://patlog.co.uk/about&quot;&gt;not being a legal requirement in the UK&lt;/a&gt; is nonetheless regularly demanded of hirers of electrical equipment by their customers - schools, councils, and other organisations who haven&#39;t caught up with the times. They&#39;ll sometimes ask to see a recent PAT certificate covering any equipment they&#39;re interested in.&lt;/p&gt;
&lt;p&gt;Having to do needless tests just as a box-checking exercise is annoying enough, but then needing to track those in a system that can generate certificates, to provide on request, makes it an even more frustrating job - hence Spencer&#39;s app.&lt;/p&gt;
&lt;p&gt;It was cool to see the app built in C# - I wrote loads of C# when I worked at &lt;a href=&quot;https://www.bouncycastlenetwork.com&quot;&gt;The Bouncy Castle Network&lt;/a&gt; albeit on the web, not desktop - but the bouncy castle world is not a particularly technically-minded one, and I knew that very few people would download the app and compile it to run on their machines.&lt;/p&gt;
&lt;p&gt;So, I set about translating the app into a Rails app that I could host for people. I made heavy use of Claude Code to parse Spencer&#39;s C# code, write a comprehensive spec, and then re-implement it as a straightforward Rails app.&lt;/p&gt;
&lt;p&gt;This wasn&#39;t &lt;em&gt;vibe coding&lt;/em&gt;. I was reviewing each change Claude suggested, regularly digging it out of corners, and writing the few fiddly bits myself. But I was definitely able to work much faster by using it to translate the existing, working app, and to write a bunch of tests.&lt;/p&gt;
&lt;p&gt;After a couple of evenings I&#39;d got a pretty good beta version up on a demo site, with a couple of extra features on top of the PDF-generating original version:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User accounts&lt;/li&gt;
&lt;li&gt;QR codes linking to the PDF version&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I initially thought I might be able to charge some nominal fee for access to the site, but Spencer rightly pointed out that nobody is making any money doing PAT tests - it&#39;s a necessary evil - and they&#39;re unlikely to want to pay a per-test fee just to host the test online.&lt;/p&gt;
&lt;p&gt;I also wasn&#39;t looking forward to having to build the Stripe integration.&lt;/p&gt;
&lt;p&gt;So, I decided to make the project entirely free. I recorded a brief overview of how the system works, and put it up on &lt;a href=&quot;https://patlog.co.uk&quot;&gt;patlog.co.uk&lt;/a&gt;, hosted on on my Gandi NixOS VPS.&lt;/p&gt;
&lt;p&gt;It contains an advertisement for myself in the footer, but is otherwise entirely gratis. The source code is released under the AGPLv3 copyleft license.&lt;/p&gt;
&lt;p&gt;So, if you need to log PAT tests, the details are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;strong&gt;&lt;a href=&quot;https://patlog.co.uk&quot;&gt;patlog.co.uk&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Source: &lt;strong&gt;&lt;a href=&quot;https://git.chobble.com/chobble/patlog&quot;&gt;git.chobble.com/chobble/patlog&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Source (mirror): &lt;strong&gt;&lt;a href=&quot;https://github.com/chobble-mirror/patlog&quot;&gt;github.com/chobble-mirror/patlog&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Adding base64 placeholder background images to eleventy-img</title>
    <link href="https://blog.chobble.com/blog/25-04-16-adding-base64-image-backgrounds-to-eleventy-img/" />
    <updated>2025-04-16T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/25-04-16-adding-base64-image-backgrounds-to-eleventy-img/</id>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Just want to skip straight to the source code? &lt;a href=&quot;https://git.chobble.com/chobble/chobble-template/src/commit/32c60a0066ec620630109a2db56c3e0d8b7306fc/src/_lib/image.js&quot;&gt;Click here&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;See this in action at &lt;a href=&quot;https://example.chobble.com&quot;&gt;example.chobble.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;a href=&quot;https://blog.chobble.com/blog/25-03-28-chobble-template/&quot;&gt;Chobble Template&lt;/a&gt; is an Eleventy static website template which uses the official &lt;a href=&quot;https://github.com/11ty/eleventy-img&quot;&gt;eleventy-img&lt;/a&gt; plugin to generate &lt;code&gt;srcset&lt;/code&gt; attributes for &lt;code&gt;img&lt;/code&gt; image tags, with &lt;code&gt;webp&lt;/code&gt; and &lt;code&gt;jpeg&lt;/code&gt; formats, at various sizes.&lt;/p&gt;
&lt;p&gt;The official plugin can either catch &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags in the source code, or I can use an &lt;code&gt;image&lt;/code&gt; shortcode in my Liquid files - this means it works for images I use as part of the template, as well as images that my clients embed in pages (as Markdown).&lt;/p&gt;
&lt;p&gt;This is &lt;em&gt;awesome&lt;/em&gt;. Generating nice thumbnails and image tags is always a painful bit of web development, and the plugin lets me outsource a lot of that thinking.&lt;/p&gt;
&lt;p&gt;But the default setup is missing a feature I think is pretty neat - &lt;strong&gt;adding a background-image CSS style of a base64-encoded low quality image placeholder (LQIP) and setting the aspect-ratio of the image&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://blog.chobble.com/img/B6UUP6xGyy-1592.avif 1592w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://blog.chobble.com/img/B6UUP6xGyy-1592.webp 1592w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://blog.chobble.com/img/B6UUP6xGyy-1592.png&quot; alt=&quot;A screenshot of the Chobble Example template before any images have loaded - in their place are low quality, blocky placeholders&quot; width=&quot;1592&quot; height=&quot;927&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Having &lt;code&gt;background-image&lt;/code&gt; with a LQIP and &lt;code&gt;aspect-ratio&lt;/code&gt; filled into the page&#39;s HTML brings some nice advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Removes &amp;quot;Layout Shift&amp;quot; as the page loads because the boxes where images go are already the right size.&lt;/li&gt;
&lt;li&gt;Pages load with a low quality image which is replaced by the high-quality one as soon as it&#39;s available, which makes them feel fast.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is especially beneficial when using &lt;a href=&quot;https://turbo.hotwired.dev/&quot;&gt;Turbo&lt;/a&gt; which lets static websites feel like single-page apps by preloading links when you hover over them. Since the LQIP is part of the page&#39;s HTML, it&#39;s instantly there.&lt;/p&gt;
&lt;p&gt;Anyway, the source code for this is &lt;a href=&quot;https://git.chobble.com/chobble/chobble-template/src/branch/main/src/_lib/image.js&quot;&gt;here&lt;/a&gt;, and below are some notes about how it works / things I ran into while building it.&lt;/p&gt;
&lt;h3 id=&quot;it-wraps-the-image-in-a-div-and-adds-the-styles-to-that&quot;&gt;It wraps the image in a div and adds the styles to that&lt;/h3&gt;
&lt;p&gt;I originally wanted to apply the background image to the &lt;code&gt;img&lt;/code&gt; tag itself, which works - as in, it applies the style, but it doesn&#39;t sort things properly and introduces some quirks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Before the image tag starts loading, nothing is visible&lt;/li&gt;
&lt;li&gt;It messes with semi-transparent borders because the background image shows through them&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So I resorted to applying the style to a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, which brings its own quirks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sometimes those images are inside &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; tags, which is fine - but a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; block-level element inside a &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; isn&#39;t - so I wrote some code to bust those divs out.&lt;/li&gt;
&lt;li&gt;Each place I styled &lt;code&gt;img&lt;/code&gt; needed to be updated to work with &lt;code&gt;.image-wrapper&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;it-doesnt-customise-the-webp-encoding-at-all&quot;&gt;It doesn&#39;t customise the webp encoding at all&lt;/h3&gt;
&lt;p&gt;I originally had a hacky solution working which created the thumbnails by calling &lt;code&gt;cwebp&lt;/code&gt;, the command line webp encoder, and then creating a JSON file to use as Eleventy data. This ran inside of a batch script at compile time. It worked, and let me customise the webp encoding as much as I liked, but it was brittle - it added more dependencies to the project, meant I had to run a bash script before build, didn&#39;t work during development, etc.&lt;/p&gt;
&lt;p&gt;The current approach just goes with whatever the &lt;code&gt;@11ty/eleventy-img&lt;/code&gt; plugin does - which, really, is fine for 32 pixel wide placeholders.&lt;/p&gt;
&lt;h3 id=&quot;the-quality-of-the-placeholders-could-be-higher&quot;&gt;The quality of the placeholders could be higher&lt;/h3&gt;
&lt;p&gt;Static websites are generally pretty lightweight, and the Chobble Template is no exception to that. This gives a fair bit of bandwidth headroom for doing things to improve the user experience - like having your LQIPs be a couple of kilobytes larger. I opted for 32 pixels wide, but 64 would still be quite small in the source code and might look a fair bit better.&lt;/p&gt;
&lt;h3 id=&quot;it-contains-a-html-transformer-and-an-image-shortcode&quot;&gt;It contains a HTML-transformer and an Image shortcode&lt;/h3&gt;
&lt;p&gt;You can install my code by coping it to your project and including it in your &lt;code&gt;.eleventy.js&lt;/code&gt; like such:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; transformImages&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; imageShortcode &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;./src/_lib/image&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addAsyncShortcode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;image&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; imageShortcode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addTransform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;processImages&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;content&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;path &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;endsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;transformImages&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The shortcode and the image transformer ultimately call the same code - &lt;code&gt;processAndWrapImage&lt;/code&gt; - a method which calls the official Eleventy image plugin to create the various encodings for the image, including the LQIP, and then wraps those in a div with the class &lt;code&gt;image-wrapper&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-script-assumes-that-all-of-your-images-live-in-src-images&quot;&gt;The script assumes that all of your images live in /src/images&lt;/h3&gt;
&lt;p&gt;You&#39;ll need to update it if yours live elsewhere. But you&#39;ll probably need to update the script anyway - it makes a fair few assumptions about widths, formats, etc which won&#39;t apply to your site.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Introducing the &quot;Chobble Template&quot;</title>
    <link href="https://blog.chobble.com/blog/25-03-28-chobble-template/" />
    <updated>2025-03-28T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/25-03-28-chobble-template/</id>
    <content type="html">&lt;p&gt;Last month I made &lt;a href=&quot;https://blog.chobble.com/blog/25-02-16-template-for-eleventy-site-with-nix/&quot;&gt;an Eleventy and Nix static site template&lt;/a&gt; - a barebones template built with Nix, which I thought I&#39;d use as a the basis for the websites I build for &lt;a href=&quot;https://chobble.com&quot;&gt;Chobble&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But after using it for a few sites I noticed I was building the same type of components over and over - everyone wants a news section, a tiered navigation, social media links, a contact form, and responsive images - and I always want to push the site to Neocities, to use &lt;a href=&quot;https://andybrewer.github.io/mvp/&quot;&gt;MVP.css&lt;/a&gt; and &lt;a href=&quot;https://turbo.hotwired.dev/&quot;&gt;Turbo&lt;/a&gt;, and to add the same few Eleventy plugins.&lt;/p&gt;
&lt;p&gt;So, I&#39;ve made the &lt;a href=&quot;https://git.chobble.com/chobble/chobble-template&quot;&gt;Chobble Template&lt;/a&gt; which I&#39;m going to use as the basis for my websites going forwards. It&#39;s a flexible (but opinionated) framework for building lots of types of static websites, featuring things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A news system with an archive page and RSS feed&lt;/li&gt;
&lt;li&gt;A product-listing system with categories and galleries&lt;/li&gt;
&lt;li&gt;A contact form using Formspark and Botpoison&lt;/li&gt;
&lt;li&gt;A reviews / testimonials system&lt;/li&gt;
&lt;li&gt;A &amp;quot;team&amp;quot; page with staff profiles&lt;/li&gt;
&lt;li&gt;Social media links with SVG icons from &lt;a href=&quot;https://icon-sets.iconify.design/hugeicons/&quot;&gt;Hugeicons&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Per-page header images and CSS styles&lt;/li&gt;
&lt;li&gt;A slightly-tweaked MVP.css&lt;/li&gt;
&lt;li&gt;A responsive design via &lt;code&gt;@media&lt;/code&gt; breakpoints&lt;/li&gt;
&lt;li&gt;Tiered navigation from Eleventy&#39;s navigation plugin&lt;/li&gt;
&lt;li&gt;An XML sitemap&lt;/li&gt;
&lt;li&gt;A &lt;a href=&quot;https://git.chobble.com/chobble/chobble-template/src/branch/main/.forgejo/workflows/build-and-deploy.yaml&quot;&gt;Forgejo action&lt;/a&gt; to build and upload to Neocities&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can see it in action at &lt;a href=&quot;https://example.chobble.com&quot;&gt;example.chobble.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The template will adjust itself based on the content available - if there&#39;s no products, news posts, or team members, it hides that bit from the navigation and doesn&#39;t generate pages for them.&lt;/p&gt;
&lt;p&gt;In the future I imagine it containing other neat features like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JSON-LD structured data&lt;/li&gt;
&lt;li&gt;Per-product contact forms&lt;/li&gt;
&lt;li&gt;Stripe payment links&lt;/li&gt;
&lt;li&gt;Nice audio and video embeds&lt;/li&gt;
&lt;li&gt;Scripts to fetch and embed Google / Checkatrade / etc reviews&lt;/li&gt;
&lt;li&gt;Better caching for even faster builds&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This will let me treat &amp;quot;static website builds&amp;quot; more like the kind of large-scale projects I&#39;m used to. Each site be based on the monolithic template which does everything, but only the relevant pages will be rendered so build times will stay fast.&lt;/p&gt;
&lt;p&gt;The template means I can go from &amp;quot;nothing&amp;quot; to &amp;quot;empty site live on a test domain&amp;quot; in just a few minutes - I just add a new site to my Neocities account, generate an API key, clone the template, and add the key to its secrets. Easy peasy!&lt;/p&gt;
&lt;p&gt;Like all of my other code, the template is licensed as AGPL3, so you can either use it as a basis for your own project, or else &lt;a href=&quot;https://chobble.com&quot;&gt;employ me&lt;/a&gt; to do that for you.&lt;/p&gt;
&lt;p&gt;Because I charge a flat hourly fee the template will either reduce the cost of building a straightforward website - or allow me to spend more time on frills and fancy aesthetics, which is what clients are really bothered about.&lt;/p&gt;
&lt;p&gt;The link again to the template is &lt;a href=&quot;https://git.chobble.com/chobble/chobble-template&quot;&gt;here&lt;/a&gt; - and if you do end up using it, it&#39;d be sweet is you could &lt;a href=&quot;https://chobble.com/contact/&quot;&gt;drop me a message&lt;/a&gt; - I&#39;d love to see it in action.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Baked beans need eight minutes in the microwave</title>
    <link href="https://blog.chobble.com/blog/25-02-23-beans-need-eight-minutes/" />
    <updated>2025-02-23T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/25-02-23-beans-need-eight-minutes/</id>
    <content type="html">&lt;p&gt;Like all Brits my main source of nutrition is our national dish - baked beans.&lt;/p&gt;
&lt;p&gt;But the sad reality is that many of my countryfolk are eating substandard beans that have been mis-cooked.&lt;/p&gt;
&lt;p&gt;This post explains how to cook your baked beans perfectly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tl;dr - Branston, eight minutes, microwave, lid.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Firstly, for &lt;em&gt;perfect&lt;/em&gt; beans they &lt;strong&gt;must&lt;/strong&gt; be &lt;a href=&quot;https://branstonbeans.co.uk/&quot;&gt;Branston&#39;s&lt;/a&gt; - no others compare. If you really can&#39;t find Branston anywhere then Morrisons own-brand ones will do, but &lt;strong&gt;do not fall for the Heinz propaganda&lt;/strong&gt; - they&#39;re taking you for a fool, trading off their once-good name to sell sad little pebbles barely even worthy of being called beans. Sickening.&lt;/p&gt;
&lt;p&gt;Secondly, you need to &lt;strong&gt;microwave the beans for eight minutes&lt;/strong&gt;, with something covering the top of your microwavable vessel to keep the moisture in. I recommend cooking in a Pyrex jug with a silicone lid.&lt;/p&gt;
&lt;p&gt;Now at this point you&#39;re probably screaming &lt;em&gt;&amp;quot;but the tins say 3 minutes!&amp;quot;&lt;/em&gt; at your screen. But the tins are wrong - that&#39;s nowhere near enough. I don&#39;t know why Branston put this inaccurate information on their product but I guess they figure all the other bean tins says three minutes and it&#39;s a competitive market, or it&#39;s because three minutes means the beans will be &lt;em&gt;&amp;quot;done&amp;quot;&lt;/em&gt; at the same time as your toast, or something.&lt;/p&gt;
&lt;p&gt;Three minutes just &lt;em&gt;heats&lt;/em&gt; the beans - booooring! At eight minutes the beans get softer and the sauce gets thicker. The full, glorious potential of the tin is achieved.&lt;/p&gt;
&lt;p&gt;Actor Steve Coogan - who I once thought was quite clever - was on the Off-Menu podcast (&lt;a href=&quot;https://www.offmenupodcast.co.uk/transcripts&quot;&gt;Episode 211&lt;/a&gt;) where he made it clear that he doesn&#39;t microwave his beans for long enough. To quote:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&amp;quot;When you microwave beans, they come out hot and hard. Hot, mini bullets in, sort of, watery tomato sauce. What you need to do is-, when you&#39;re doing a breakfast, the first thing you put on is the beans and really let them break down. It&#39;s almost like-, almost to the level of mushy peas but not quite. So they&#39;re sort of mushy beans. Then they&#39;re much nicer to put on your breakfast. So don&#39;t-, anyone who puts beans in the microwave I think is-, I know they&#39;re probably-, maybe they&#39;re busy and they probably don&#39;t like people saying that but I just think it&#39;s about quality of life at the end of the day.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You&#39;re right about the beans being better when they break down, Steve, but you &lt;strong&gt;can&lt;/strong&gt; achieve that in the microwave - it just takes &lt;strong&gt;eight minutes&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;You might be tempted to follow Steve Coogan&#39;s advice about cooking beans in a pan for a long time, but here are the reasons that eight minutes in the microwave is better:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;You don&#39;t have to stir.&lt;/strong&gt; Try cooking beans in a saucepan for ages without stirring! Some will get stuck to the pan. That&#39;s wasted beans!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;There&#39;s less mess.&lt;/strong&gt; No sauce splatters out because you&#39;ve put a cover on, and you don&#39;t have to get a wooden spoon dirty.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;It&#39;s faster.&lt;/strong&gt; The microwave starts cooking your beans immediately, but with a saucepan it takes a while to get going.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;It&#39;s more reliable.&lt;/strong&gt; Eight minutes in the microwave will give you perfect beans 100% of the time - good luck achieving that on a stove!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alright - I&#39;ve said my piece. You can take it on board and have superior baked beans from now on, or ignore it and live a life of sub-par beans. It&#39;s your call.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Spamming Works!*</title>
    <link href="https://blog.chobble.com/blog/25-02-19-spamming-works/" />
    <updated>2025-02-19T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/25-02-19-spamming-works/</id>
    <content type="html">&lt;p&gt;&lt;em&gt;* at least in terms of Google rankings in the very short term.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When I talk to small business owners about search engine optimisation and online marketing, I tell them to stick to &lt;em&gt;&amp;quot;white hat&amp;quot;&lt;/em&gt; methods - wholesome, positive changes they can make to provide more value to visitors and to hopefully push their website up the search rankings.&lt;/p&gt;
&lt;p&gt;This is in contrast to &lt;em&gt;&amp;quot;black hat&amp;quot;&lt;/em&gt; SEO which involves things like stuffing your site full of keywords, using generative AI to create dozens of pages, hiding text on your site, paying for links, and so on - shady and spammy activities that Google and other search engines &lt;em&gt;really&lt;/em&gt; don&#39;t want you to do.&lt;/p&gt;
&lt;p&gt;The unfortunate reality of online marketing, though, is that the shady stuff can work really well - that&#39;s why Google doesn&#39;t want you to do it! Google &lt;em&gt;knows&lt;/em&gt; that its algorithm can be manipulated by spammy tactics, and so they penalise the sites that take those approaches &lt;em&gt;when they catch them&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That last four words there are key - I&#39;m writing this in 2025 when Google&#39;s usefulness is at an all-time low (many articles have been written about its crappy results) and its search results are often filled with garbage, so clearly Google isn&#39;t doing a very good job of actually &amp;quot;catching&amp;quot; spammers in a lot of cases. Maybe they&#39;re trying their best? I doubt it, since &lt;a href=&quot;https://kagi.com&quot;&gt;Kagi&lt;/a&gt; is a much tinier search company and their results are way better, but for now lets give Google the benefit of the doubt.&lt;/p&gt;
&lt;p&gt;Given that the shady stuff &lt;strong&gt;can&lt;/strong&gt; work really well to bump your site up the rankings, should I instead be advising businesses to spam instead of improving their site? Spamming probably has the better &amp;quot;return on investment&amp;quot; in the very short term, after all.&lt;/p&gt;
&lt;p&gt;I still think the answer is &lt;strong&gt;&amp;quot;no, you shouldn&#39;t spam&amp;quot;&lt;/strong&gt; - and here are my reasons:&lt;/p&gt;
&lt;h2 id=&quot;the-downsides-of-being-penalised-are-too-intense&quot;&gt;The downsides of being penalised are too intense&lt;/h2&gt;
&lt;p&gt;Most small businesses are really attached to their domain name - they have it printed on their uniforms, their vans, their business cards, and it&#39;s probably mentioned in dozens of places online.&lt;/p&gt;
&lt;p&gt;Falling foul of Google&#39;s spam-detection algorithms can mean that domain is effectively erased from search results. Getting listed again can be a gruelling, multi-month process involving manual appeals, link disavowals, and lots of waiting around - and it might never happen. The more spamming that was done on your behalf, the harder it will be to undo things if you get caught.&lt;/p&gt;
&lt;p&gt;This would be &lt;em&gt;ruinous&lt;/em&gt; to many small businesses. Even if most of your customers come from &amp;quot;word of mouth&amp;quot;, you want them to be able to search for your business name (or a close-enough approximation of it) and find your site rather than having to remember the exact domain. Being blocked from search results can make this impossible.&lt;/p&gt;
&lt;h2 id=&quot;googles-algorithms-change-all-the-time&quot;&gt;Google&#39;s algorithms change all the time&lt;/h2&gt;
&lt;p&gt;Yes, in 2025 it&#39;s fair to say that Google&#39;s results are full of spam. But will that be the case next year, or the year after? We can hope not - and indeed, Google have rolled out algorithm updates in the past (like &lt;a href=&quot;https://en.wikipedia.org/wiki/Google_Panda&quot;&gt;Panda&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Google_Penguin&quot;&gt;Penguin&lt;/a&gt;) which totally upended the &amp;quot;search engine optimisation&amp;quot; world and heavily penalised sites doing things they&#39;d totally gotten away with before then.&lt;/p&gt;
&lt;h2 id=&quot;keyword-stuffed-text-can-look-really-gross&quot;&gt;Keyword-stuffed text can look really gross&lt;/h2&gt;
&lt;p&gt;As someone who reads lots of web pages every day I&#39;m pretty good at determining whether the content provides any real value, or if it&#39;s just waffle written for search engine optimisation purposes.&lt;/p&gt;
&lt;p&gt;While I can forgive the occasional shoehorned insertion of a keyword into an otherwise normal sentence, I&#39;ll be very dissuaded from using a company&#39;s services if their website&#39;s text is so full of keywords that it becomes difficult to understand what the page is actually about.&lt;/p&gt;
&lt;p&gt;I think a lot of people are like me, who read the content of the websites they visit and evaluate whether it was written for a human or an algorithm.&lt;/p&gt;
&lt;h2 id=&quot;many-people-can-spot-ai-generated-content&quot;&gt;Many people can spot AI-generated content&lt;/h2&gt;
&lt;p&gt;A study last year showed that &lt;a href=&quot;https://www.marketingtechnews.net/news/50-of-consumers-can-detect-ai-generated-content/&quot;&gt;50% of people can spot AI-generated content&lt;/a&gt; - and I doubt that when those people see websites full of it they think &lt;em&gt;&amp;quot;oh, what a novel use of large language models&amp;quot;&lt;/em&gt;. They instead probably think something like &lt;em&gt;&amp;quot;oh, this website owner has cut corners on their content, so I wonder which other corners they&#39;ve cut&amp;quot;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;If you&#39;re one of the people who can&#39;t spot AI-generated content and doesn&#39;t think it reads differently from human-created text, you might think that everyone is like this! But that&#39;s not the case - many people are pretty good at telling if something was written by a chatbot or a human.&lt;/p&gt;
&lt;h2 id=&quot;a-focus-on-spam-means-a-lack-of-focus-on-value&quot;&gt;A focus on spam means a lack of focus on value&lt;/h2&gt;
&lt;p&gt;The best way for a business to do well is to listen to the needs of its potential customers and to orient itself to match those needs. This is what &lt;em&gt;&amp;quot;white hat&amp;quot;&lt;/em&gt; SEO is all about.&lt;/p&gt;
&lt;p&gt;But &amp;quot;focus&amp;quot; is a finite resource. If you&#39;re spending time focusing on paid backlinks, or keyword density, or other spammy techniques, that&#39;s time you&#39;re &lt;em&gt;not&lt;/em&gt; spending making your business more appealing to your future customers.&lt;/p&gt;
&lt;p&gt;If you spam the bejeezus out of your site and get it to the #1 spot for a competitive term, but you&#39;ve neglected to focus on the visitor experience once they&#39;re actually on your site, you&#39;re onto a loser. All the visitors in the world won&#39;t help if none of them convert into customers.&lt;/p&gt;
&lt;h2 id=&quot;you-might-risk-legal-penalities&quot;&gt;You might risk legal penalities&lt;/h2&gt;
&lt;p&gt;Some spammy tactics are actually illegal in the UK. For example, &lt;a href=&quot;https://www.gov.uk/government/publications/online-reviews-and-endorsements-advice-for-businesses/online-reviews-giving-consumers-the-full-picture&quot;&gt;commissioning or writing fake reviews is against the law&lt;/a&gt;, and while it&#39;s still somewhat unlikely that you&#39;ll get caught, the reputational damage if you did could be huge.&lt;/p&gt;
&lt;p&gt;Similarly, I imagine it&#39;s only a matter of time before someone gets charged with copyright or trademark infringement after using generative AI to write content for their business - after all, the AI models are trained on copywritten works.&lt;/p&gt;
&lt;h2 id=&quot;spam-makes-the-internet-worse-for-everyone&quot;&gt;Spam makes the internet worse for everyone&lt;/h2&gt;
&lt;p&gt;Maybe this should have been the first point, but speaking as a lifelong nerd - spam makes the internet worse in general. It&#39;s hard to Google things now because the results are full of spam. You can&#39;t trust Reddit threads any more because there&#39;s so many attention-farming AI spammers using the site. My social media feeds are full of get-rich-quick schemes and people promising the earth.&lt;/p&gt;
&lt;p&gt;Basically, spam sucks, and makes every internet user&#39;s day a bit less pleasant, and I think it&#39;s very legit to not to want to contribute to that.&lt;/p&gt;
&lt;h2 id=&quot;closing-thoughts&quot;&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;I think we&#39;re going to continue to see spam &amp;quot;work&amp;quot; until Google prioritises the quality of its results over its advertisement revenue. But that doesn&#39;t mean you should become a spammer. The downsides to your business are very real and could be catastrophic!&lt;/p&gt;
&lt;p&gt;Doing things the &amp;quot;right&amp;quot; way can be a slog, sure, but it won&#39;t be risky and it doesn&#39;t involve learning anything particularly technical or esoteric. Just keep the focus on providing real value to site visitors, and try and keep the faith that it will pay off even if you&#39;re not seeing dramatic gains every day - and it &lt;em&gt;will&lt;/em&gt; pay off, eventually - I promise!*&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;*not a binding promise - maybe it won&#39;t pay off, maybe we&#39;re doomed to a spammy internet forever, maybe the robots will win, ahh nooooo!&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>I made an Eleventy &amp; Nix static site template</title>
    <link href="https://blog.chobble.com/blog/25-02-16-template-for-eleventy-site-with-nix/" />
    <updated>2025-02-16T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/25-02-16-template-for-eleventy-site-with-nix/</id>
    <content type="html">&lt;p&gt;I&#39;ve had to create a new Eleventy site on NixOS a bunch of times lately, so I figured I make a template to save myself repeating the same steps over and over.&lt;/p&gt;
&lt;p&gt;Hopefully this will come in helpful for anyone else who&#39;s attempting to build Eleventy (or any other npm-based site generator) on NixOS.&lt;/p&gt;
&lt;p&gt;The source code for the template I made is here: &lt;a href=&quot;https://git.chobble.com/chobble/empty-eleventy-nix-site&quot;&gt;git.chobble.com/chobble/empty-eleventy-nix-site&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To get started just run &lt;code&gt;git clone https://git.chobble.com/chobble/empty-eleventy-nix-site&lt;/code&gt;, then &lt;code&gt;cd&lt;/code&gt; to the directory, run &lt;code&gt;direnv allow&lt;/code&gt; or &lt;code&gt;nix develop&lt;/code&gt;, and then &lt;code&gt;serve&lt;/code&gt; to get a server running locally.&lt;/p&gt;
&lt;p&gt;You can also run &lt;code&gt;nix-build&lt;/code&gt; to reproducibly build the site using Nix - I call this from my &lt;a href=&quot;https://blog.chobble.com/blog/24-12-02-site-builder-updates/&quot;&gt;NixOS site builder&lt;/a&gt; which powers the Chobble server.&lt;/p&gt;
&lt;p&gt;To quote its &lt;code&gt;README.md&lt;/code&gt;:&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;This template should let you get started with the Eleventy static site builder on NixOS / Nix, really easily.&lt;/p&gt;
&lt;p&gt;Featuring Nix&#39;y features like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://direnv.net/&quot;&gt;direnv&lt;/a&gt; support via &lt;code&gt;flake.nix&lt;/code&gt; - run &lt;code&gt;direnv allow&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;or run &lt;code&gt;nix develop&lt;/code&gt; if you don&#39;t have direnv&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nix-build&lt;/code&gt; support using &lt;code&gt;flake-compat&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;serve&lt;/code&gt; shell script to run Eleventy and SASS locally&lt;/li&gt;
&lt;li&gt;&lt;code&gt;build&lt;/code&gt; shell script to build the site into &lt;code&gt;_site&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And Eleventy features like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Canonical URLs&lt;/li&gt;
&lt;li&gt;A directory to store favicon cruft&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;_data/site.json&lt;/code&gt; metadata store&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;collection.images&lt;/code&gt; collection of the files in &lt;code&gt;src/images&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;changing-packages&quot;&gt;Changing Packages&lt;/h2&gt;
&lt;p&gt;If you want to change the packages in &lt;code&gt;packages.json&lt;/code&gt;, here&#39;s the steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Remove all &lt;code&gt;nodeModules&lt;/code&gt; lines from &lt;code&gt;node-deps.nix&lt;/code&gt; and &lt;code&gt;flake.nix&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;direnv reload&lt;/code&gt; or &lt;code&gt;nix develop&lt;/code&gt; to get a dev shell&lt;/li&gt;
&lt;li&gt;Add the new packages to &lt;code&gt;node-deps.nix&lt;/code&gt; and run &lt;code&gt;direnv reload&lt;/code&gt; to re-build &lt;code&gt;packages.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;yarn -l&lt;/code&gt; to create a new &lt;code&gt;yard.lock&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Re-add the &lt;code&gt;nodeModule&lt;/code&gt; lines to &lt;code&gt;node-deps.nix&lt;/code&gt; and &lt;code&gt;flake.nix&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;upgrading-packages&quot;&gt;Upgrading Packages&lt;/h2&gt;
&lt;p&gt;This is a little fiddlier:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Remove all &lt;code&gt;nodeModules&lt;/code&gt; lines from &lt;code&gt;node-deps.nix&lt;/code&gt; and &lt;code&gt;flake.nix&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Copy the generated &lt;code&gt;package.json&lt;/code&gt; to your clipboard&lt;/li&gt;
&lt;li&gt;Delete &lt;code&gt;package.json&lt;/code&gt; to remove the symbolic link&lt;/li&gt;
&lt;li&gt;Paste your clipbard back into a new &lt;code&gt;package.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;yarn upgrade&lt;/code&gt; to create a new &lt;code&gt;yarn.lock&lt;/code&gt; and update &lt;code&gt;package.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Copy those new version numbers from &lt;code&gt;package.json&lt;/code&gt; to &lt;code&gt;node-deps.nix&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Re-add the &lt;code&gt;nodeModule&lt;/code&gt; lines to &lt;code&gt;node-deps.nix&lt;/code&gt; and &lt;code&gt;flake.nix&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;..I do intend to make those steps simpler some day. If you&#39;ve got ideas of how I could, please contact me!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Adding social preview images to my Eleventy blog</title>
    <link href="https://blog.chobble.com/blog/25-01-19-social-preview-images/" />
    <updated>2025-01-19T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/25-01-19-social-preview-images/</id>
    <content type="html">&lt;p&gt;I just followed &lt;a href=&quot;https://www.mathew-paul.nz/posts/eleventy-html-validation/&quot;&gt;this excellent guide by Matthew Paul&lt;/a&gt; to add social media preview images to this blog - and it worked great!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://git.chobble.com/chobble/blog/commit/c09b3d099d10ea866f0a86335d2b4d70994accc4&quot;&gt;Here is the commit which did it&lt;/a&gt; - I tweaked Mat&#39;s guide a bit because my blog is set up with ESM rather than CommonJS - hopefully if you&#39;re also using an ESM setup then you can find some inspiration here.&lt;/p&gt;
&lt;p&gt;Because this blog is built by &lt;code&gt;nix-build&lt;/code&gt; I also needed to tweak things a little to add the &lt;code&gt;ubuntu&lt;/code&gt; font to the site&#39;s &lt;a href=&quot;https://git.chobble.com/chobble/blog/src/branch/main/default.nix&quot;&gt;default.nix&lt;/a&gt; and to use that font inside &lt;a href=&quot;https://git.chobble.com/chobble/blog/src/branch/main/content/social-preview.njk&quot;&gt;social-preview.njk&lt;/a&gt; - without that, no fonts were accessible and the text rendered as tiny squares.&lt;/p&gt;
&lt;p&gt;My blog is based on this template: &lt;a href=&quot;https://github.com/11ty/eleventy-base-blog&quot;&gt;https://github.com/11ty/eleventy-base-blog&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here&#39;s the final image for this post:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&#39;Social media preview images&#39; blog post preview image&quot; src=&quot;https://blog.chobble.com/social-preview-images/25-01-19-social-preview-images.jpeg&quot;&gt;&lt;/p&gt;
&lt;p&gt;Very satisfying!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Libregig</title>
    <link href="https://blog.chobble.com/blog/25-01-14-libregig/" />
    <updated>2025-01-14T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/25-01-14-libregig/</id>
    <content type="html">&lt;p&gt;I&#39;ve started &lt;a href=&quot;https://chobble.com&quot;&gt;Chobble&lt;/a&gt; to bring some income in by building websites and providing tech consultancy, but the other project I&#39;ve been working on and will be putting a lot of time into in 2025 is &amp;quot;Libregig&amp;quot;.&lt;/p&gt;
&lt;p&gt;Libregig is a Ruby on Rails webapp for managing the day-to-day of being in a band - events, members, set lists, documents, equipment, rehearsals, etc. It will (eventually) do things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Let &amp;quot;manager&amp;quot; users invite other users to edit / view their bands / events&lt;/li&gt;
&lt;li&gt;Sync member-specific calendars via iCal / Google Calendar&lt;/li&gt;
&lt;li&gt;Provide a WhatsApp / Signal bot to keep a group chat updated&lt;/li&gt;
&lt;li&gt;Do email notifications about updates to events&lt;/li&gt;
&lt;li&gt;Allow &#39;Promoter&#39; users to plan tours&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unlike the existing band management tools, Libregig is / will eventually be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open source and AGPLv3 licensed&lt;/li&gt;
&lt;li&gt;Federated, so tours can be planned across instances&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I plan on hosting a paid-for instance, and I will provide instructions for others to host their own instances too. I will price it as low as possible and might set up a non-profit business to manage this.&lt;/p&gt;
&lt;p&gt;My goal is for technically-minded music nerds like myself to be able to generate a modest income by administrating paid-for Libregig servers for their users.&lt;/p&gt;
&lt;p&gt;Of course, since it&#39;s AGPLv3-licensed and fully open, you could also host your own instance too without paying anyone.&lt;/p&gt;
&lt;p&gt;My goal is to create a really useful bit of software which is immune to takeover by corporate interests, by being open and federated. Basically, think &amp;quot;Mastodon for bands&amp;quot; - although I&#39;m not sure whether it&#39;ll actually use the ActivityPub protocol yet.&lt;/p&gt;
&lt;p&gt;Eventually, and this is a long shot but I think is doable, promoters could sell tickets through Libregig, and it could help undermine scummy companies like TicketMaster.&lt;/p&gt;
&lt;p&gt;There&#39;s not a lot to show yet, but you can check out the Git repo at &lt;a href=&quot;https://git.chobble.com/chobble/libregig&quot;&gt;git.chobble.com/chobble/libregig&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It&#39;s built with Ruby on Rails and I&#39;m aiming to keep its codebase as simple as possible to encourage contributions.&lt;/p&gt;
&lt;p&gt;Sometime soon I&#39;ll aim to get a website together detailing all of the above.&lt;/p&gt;
&lt;p&gt;This all feels quite ambitious, but I keep reminding myself that much of the open source software I use every day started with pretty humble beginnings. And I have enough savings to work on this for a long while without it bringing in any income, so there&#39;s no great rush.&lt;/p&gt;
&lt;p&gt;If you&#39;ve got any ideas about other cool features that Libregig should include, please let me know, and if you&#39;d like to contribute to the codebase, that&#39;d be sweeeeeet - there&#39;s a lot to do!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>I started a Patreon</title>
    <link href="https://blog.chobble.com/blog/25-01-13-patreon-group/" />
    <updated>2025-01-13T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/25-01-13-patreon-group/</id>
    <content type="html">&lt;p&gt;In my previous roles my knowledge about how to optimise websites to do well on Google has been something that customers / bosses have really appreciated.&lt;/p&gt;
&lt;p&gt;I much prefer the &amp;quot;teach a man to fish&amp;quot; approach to online marketing - it&#39;s much more satisfying to educate people about how to optimise their sites than it is to do the work myself, and it also avoids the tricky problem of working with two clients who compete against each other, which always feels gross.&lt;/p&gt;
&lt;p&gt;So! I&#39;ve started &lt;strong&gt;&lt;a href=&quot;https://www.patreon.com/Chobble&quot;&gt;a Patreon page at patreon.com/Chobble&lt;/a&gt;&lt;/strong&gt; to share this advice, host Q&amp;amp;As and live streams, and field questions. It costs £20 per month for full access, which feels like good value to me.&lt;/p&gt;
&lt;p&gt;&amp;quot;Search engine optimisation&amp;quot; often gets a bad rep, because the industry is full of people taking spammy shortcuts, and search engine results often end up full of low-content pages jostling for the top spot.&lt;/p&gt;
&lt;p&gt;But it doesn&#39;t have to be that way!&lt;/p&gt;
&lt;p&gt;There are ways to rank well which don&#39;t involve anything spammy or gross. Instead, we can focus on high quality content which addresses the needs of users, performant and clear pages that direct visitors to the information they need, and organic networking and link-building across websites that make contextual sense.&lt;/p&gt;
&lt;p&gt;Those are the kind of tips I&#39;ll be sharing in the group - simple, practical things you can do to make your site more enticing to potential customers.&lt;/p&gt;
&lt;p&gt;You don&#39;t need any specific technical skills, just the ability to edit your website and the ability to set some time aside to do it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://www.patreon.com/Chobble&quot;&gt;Click here to join for £20 per month! (no contract)&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Donating 10% to the Against Malaria Foundation</title>
    <link href="https://blog.chobble.com/blog/25-01-04-against-malaria/" />
    <updated>2025-01-04T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/25-01-04-against-malaria/</id>
    <content type="html">&lt;p&gt;For the last decade or so I have pledged to donate at least 10% of my income to charitable causes, and my work as a freelancer will be no different.&lt;/p&gt;
&lt;p&gt;As &#39;Chobble&#39; that 10% will go to the &lt;a href=&quot;https://www.againstmalaria.com&quot;&gt;Against Malaria Foundation&lt;/a&gt; who represent one of the best value for money ways of saving lives - they buy insecticide-treated nets for people to sleep under, with an average cost of just $5,500 per life saved.&lt;/p&gt;
&lt;p&gt;(You can search for me - Stefan Burke - on &lt;a href=&quot;https://www.againstmalaria.com/Donations.aspx&quot;&gt;their list of sponsors&lt;/a&gt; to see my previous donations)&lt;/p&gt;
&lt;p&gt;I am not sharing this to brag - plenty of people donate much more than me or work in harder jobs than mine, on the front lines of care.&lt;/p&gt;
&lt;p&gt;But my dad often told me - and studies agree - that you should not be shy about your donations to good causes. Being vocal encourages others to chip in.&lt;/p&gt;
&lt;p&gt;So if you can spare a percentage of your income, please consider donating it to &lt;a href=&quot;https://www.givewell.org/&quot;&gt;an effective charity&lt;/a&gt;!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Hiding Adrian Chiles&#39; posts on theguardian.com</title>
    <link href="https://blog.chobble.com/blog/24-12-19-blocking-chiles/" />
    <updated>2024-12-19T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/24-12-19-blocking-chiles/</id>
    <content type="html">&lt;p&gt;You can use &lt;a href=&quot;https://github.com/gorhill/uBlock&quot;&gt;uBlock Origin&lt;/a&gt; (the &lt;em&gt;essential&lt;/em&gt; ad-blocking browser extension) to filter web page elements on the text they contain, by using their pseudo-selector, &lt;code&gt;:has-text(/foo/)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This means you can easily hide Adrian Chiles&#39; posts from The Guardian homepage, by copying the following line to &amp;quot;My Filters&amp;quot; within the uBlock Origin dashboard:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;www.theguardian.com##div&amp;gt;div:has-text(/Adrian Chiles/)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Easy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Getting Thingino webhooks to play nice with ntfy</title>
    <link href="https://blog.chobble.com/blog/24-12-05-thingino-camera-send-to-ntfy/" />
    <updated>2024-12-05T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/24-12-05-thingino-camera-send-to-ntfy/</id>
    <content type="html">&lt;p&gt;A quick fix for anyone else who&#39;s got a &lt;a href=&quot;https://thingino.com/&quot;&gt;Thingino&lt;/a&gt; firmware&#39;d camera and wants to send notifications with screenshots to &lt;a href=&quot;https://ntfy.sh/&quot;&gt;ntfy&lt;/a&gt; via Thingino&#39;s &#39;webhook&#39; option.&lt;/p&gt;
&lt;p&gt;By default it won&#39;t work, because behind the scenes Thingingo calls &lt;code&gt;curl -F &#39;image=@$attachment&#39;&lt;/code&gt; and sends the image as form data. You will receive an &lt;code&gt;attachment.bin&lt;/code&gt; file which won&#39;t open:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://blog.chobble.com/img/Ydy52dMKpj-709.avif 709w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://blog.chobble.com/img/Ydy52dMKpj-709.webp 709w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://blog.chobble.com/img/Ydy52dMKpj-709.png&quot; alt=&quot;A screenshot of the ntfy interface showing &#39;You received a file: attachment.bin&#39;&quot; width=&quot;709&quot; height=&quot;360&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;To play nicely with &lt;code&gt;ntfy&lt;/code&gt; you need to change this to &lt;code&gt;-F&lt;/code&gt; to a &lt;code&gt;-T&lt;/code&gt; for &amp;quot;upload file&amp;quot;. And while you&#39;re in there you might as well add a nice title and icon too:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://blog.chobble.com/img/5U7i5el0NO-709.avif 709w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://blog.chobble.com/img/5U7i5el0NO-709.webp 709w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://blog.chobble.com/img/5U7i5el0NO-709.png&quot; alt=&quot;A screenshot of the ntfy interface showing &#39;Front door motion detected. You received a file: camera.jpg&#39; and the actual photo itself&quot; width=&quot;709&quot; height=&quot;682&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;You&#39;ll need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ssh root@ip_address&lt;/code&gt; to log into the camera, and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vi /usr/sbin/send2webhook&lt;/code&gt; to edit the webhook code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Change &lt;a href=&quot;https://github.com/themactep/thingino-firmware/blob/master/overlay/lower/usr/sbin/send2webhook#L36&quot;&gt;line 36&lt;/a&gt; from &lt;code&gt;build_cmd &amp;quot;-F &#39;image=@$attachment&#39;&amp;quot;&lt;/code&gt; to something like:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;build_cmd &lt;span class=&quot;token string&quot;&gt;&quot;-T &#39;&lt;span class=&quot;token variable&quot;&gt;$attachment&lt;/span&gt;&#39; -H &#39;Filename: camera.jpg&#39;&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Tag: camera_flash&#39;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Title: Front door motion detected&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save and you&#39;re done. Easy!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Nix Site Builder Updates</title>
    <link href="https://blog.chobble.com/blog/24-12-02-site-builder-updates/" />
    <updated>2024-12-02T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/24-12-02-site-builder-updates/</id>
    <content type="html">&lt;p&gt;An update on my efforts to get everything in place to run a libre / ethical / transparent web development agency. I took it easy for a couple of weeks but I&#39;m trying to ramp up the pace now.&lt;/p&gt;
&lt;p&gt;I&#39;ve spent a lot of time working on &lt;a href=&quot;https://git.chobble.com/chobble/nixos-site-builder/&quot;&gt;nixos-site-builder&lt;/a&gt; over the last few days. I&#39;ll use this alongside &lt;a href=&quot;https://git.chobble.com/chobble/chobble-server&quot;&gt;chobble-server&lt;/a&gt; for each Chobble-hosted site, where they will perform their duties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hosting the Git repositories&lt;/li&gt;
&lt;li&gt;Building sites via &lt;code&gt;nix build&lt;/code&gt; (or just &lt;code&gt;cp&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Serving over HTTPS via Caddy&lt;/li&gt;
&lt;li&gt;Pushing to Neocities and other hosts&lt;/li&gt;
&lt;li&gt;Uploading to SFTP servers&lt;/li&gt;
&lt;li&gt;Push-mirroring Git repos elsewhere as a backup&lt;/li&gt;
&lt;li&gt;Alerting on failures&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As I&#39;ve migrated sites over to this server (&lt;a href=&quot;https://git.chobble.com/hosted-by-chobble&quot;&gt;these ones&lt;/a&gt;) I&#39;ve improved the site builder script to add features like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;site&lt;/code&gt; command (&lt;a href=&quot;https://git.chobble.com/chobble/nixos-site-builder/src/branch/main/lib/mkSiteCommands.nix&quot;&gt;file&lt;/a&gt;) with:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;site list&lt;/code&gt; to list sites by domain alongside their &lt;code&gt;systemd&lt;/code&gt; service names&lt;/li&gt;
&lt;li&gt;&lt;code&gt;site status example.com&lt;/code&gt; for &lt;code&gt;systemctl status &amp;lt;that service&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;site restart example.com&lt;/code&gt; for &lt;code&gt;systemctl restart &amp;lt;that service&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Only replacing the site if the &lt;code&gt;nix-build&lt;/code&gt; command works (&lt;a href=&quot;https://git.chobble.com/chobble/nixos-site-builder/commit/b6f7734d556ed14f1c55c6cc4df08ab1d14cd445&quot;&gt;commit&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Building and pushing to Neocities via a &lt;code&gt;host&lt;/code&gt; and &lt;code&gt;apiKey&lt;/code&gt; option&lt;/li&gt;
&lt;li&gt;More tests&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using NixOS for the setup / builds will (eventually) mean everything is documented in a useful way for other would-be hosters whether they use NixOS or not. I might also add build scripts for other setups - and test them in VMs in NixOS. It will also help me learn Nix - cool!&lt;/p&gt;
&lt;p&gt;I also started using &lt;a href=&quot;https://bunny.net/&quot;&gt;Bunny CDN&lt;/a&gt; on the &lt;a href=&quot;https://www.veganprestwich.co.uk&quot;&gt;Vegan Prestwich&lt;/a&gt; site. It&#39;s totally unneccessary there, but I&#39;m doing it to get familiar with Bunny who seem like a great choice for a CDN host, which I&#39;ll need at some point. Turns out it was dead easy! You just set Bunny to mirror from an origin host (I chose the &lt;code&gt;www&lt;/code&gt;-less version but will change this eventually) and then you set a &lt;code&gt;CNAME&lt;/code&gt; DNS record to the &lt;code&gt;*.b-cdn.net&lt;/code&gt; Bunny domain you&#39;ll see when you create a &amp;quot;&lt;a href=&quot;https://dash.bunny.net/cdn&quot;&gt;Pull Zone&lt;/a&gt;&amp;quot;.&lt;/p&gt;
&lt;p&gt;You can&#39;t set &lt;code&gt;CNAME&lt;/code&gt; records for your root domains which means if you want Bunny to cache the full site including HTML in the &lt;a href=&quot;https://bunny.net/blog/how-aname-dns-records-affect-cdn-routing/&quot;&gt;most optimal&lt;/a&gt; way you need to ideally host the site at &lt;code&gt;www&lt;/code&gt;, and then you need to figure out the &lt;code&gt;www-less&lt;/code&gt; to &lt;code&gt;www&lt;/code&gt; redirect yourself somehow. I&#39;ve set &lt;code&gt;&amp;lt;link rel=&amp;quot;canonical&amp;quot;&amp;gt;&lt;/code&gt; tags for now, but really should do a 301 redirect to the &lt;code&gt;www&lt;/code&gt; instead. Soon!&lt;/p&gt;
&lt;p&gt;Next up I&#39;m thinking of installing &lt;a href=&quot;https://www.goatcounter.com&quot;&gt;Goatcounter&lt;/a&gt;, which &lt;a href=&quot;https://www.apalrd.net/posts/2023/studio_website/#goatcounter-service&quot;&gt;I can set up to parse my Caddy logs&lt;/a&gt; to give me privacy-protecting lightweight analytics, which I&#39;ll need eventually so may as well figure out. Parsing the logs will give way less fidelity than running the Goatcounter Javascript, which itself will give way less than running some big corpo spyware like Google Analytics, but that suits me great.&lt;/p&gt;
&lt;p&gt;I&#39;m also taking tentative steps towards running my Ruby on Rails app on NixOS as a systemd service, and thinking about emails, versioning, and database backups - and how long I can go without dealing with them.&lt;/p&gt;
&lt;p&gt;On the business side I&#39;ve done a lot of thinking about how pricing should work. My goal is to be radically transparent - which means that I won&#39;t do any of the common tricks of the trade:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adding my own margin to off the shelf templates, outsourced labour, etc.&lt;/li&gt;
&lt;li&gt;Being opague about the actual cost of hosting and support.&lt;/li&gt;
&lt;li&gt;Charging for small changes to the site that the client could do.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But I still need to earn a wage, so I need to figure out an hourly rate that lets me do that, and think of a way to provide attentive support and guidance that doesn&#39;t leave me working for free.&lt;/p&gt;
&lt;p&gt;I&#39;m currently thinking of charging about £200 per hour, and £100 for charities. Those feel like high numbers but I think that&#39;s probably what&#39;s neccessary to cover my costs for now.&lt;/p&gt;
&lt;p&gt;And Support-wise I&#39;m thinking something like, you can either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get two hours of free support each month for some low cost, like £20&lt;/li&gt;
&lt;li&gt;Don&#39;t pay for support, sort the hosting out yourself and only pay me ad-hoc when needed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both options would be totally fine by me, but if I could get 100 customers paying £20 a month for support that&#39;d be an awesome baseline income.&lt;/p&gt;
&lt;p&gt;Anyway, that&#39;s enough for today. Over and out!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Hardening NixOS</title>
    <link href="https://blog.chobble.com/blog/24-11-28-hardening-nixos/" />
    <updated>2024-11-28T00:00:00Z</updated>
    <id>https://blog.chobble.com/blog/24-11-28-hardening-nixos/</id>
    <content type="html">&lt;p&gt;Today I got stuck into locking my new NixOS server down - if I&#39;m going to host site for customers it needs to be extra secure, and I need to be notified if something breaks.&lt;/p&gt;
&lt;p&gt;I used &lt;a href=&quot;https://xeiaso.net/blog/paranoid-nixos-2021-07-18/&quot;&gt;this&lt;/a&gt; great guide by Xe for inspiration.&lt;/p&gt;
&lt;p&gt;Changes I made were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adding Tailscale SSH (achieved with &lt;code&gt;tailscale up --ssh&lt;/code&gt;) and fiddling around with my access rules&lt;/li&gt;
&lt;li&gt;Locking non-Tailscale SSH down to my home IP address&lt;/li&gt;
&lt;li&gt;Securing SSH in general - disabling things I don&#39;t use&lt;/li&gt;
&lt;li&gt;Sending notifications via NTFY whenever certain systemd services fails&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&#39;s the Chobble server configuration as it stands, with secrets redacted:&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;settings-nix&quot;&gt;settings.nix&lt;/h1&gt;
&lt;pre class=&quot;language-nix&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-nix&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# This is a NixOS configuration file for a server that runs:&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# - Forgejo (git hosting)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# - Caddy (reverse proxy)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# - Service monitoring with failure notifications&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# - Multiple static websites&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  config&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  lib&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  pkgs&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# SERVICE MONITORING CONFIGURATION&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;# These core services will always be monitored for failures&lt;/span&gt;
  baseServices &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# Git hosting service&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;forgejo&quot;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# Web server/reverse proxy&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;caddy&quot;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# Test service that always fails (for monitoring testing)&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;always-fails&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;# Get list of site builder services from the site-builder configuration.&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# Only included if site-builder is enabled.&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# Converts domains like &quot;example.com&quot; into service names like&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# &quot;example-com-builder&quot;&lt;/span&gt;
  siteBuilderServices &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;optionals
    config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;services&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;site&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;enable
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;domain&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;${lib.replaceStrings [&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;] [&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;] domain}-builder&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;builtins&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;attrNames&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;services&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;site&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sites&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;# Complete list of all services that should be monitored&lt;/span&gt;
  monitoredServices &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; baseServices &lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt; siteBuilderServices&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;# Creates a monitoring configuration for a single service&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# Input: service name (like &quot;forgejo&quot;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# Output: configuration that adds failure monitoring to that service&lt;/span&gt;
  monitorConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; lib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nameValuePair
    name
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      unitConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;OnFailure &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# %n is replaced with the service name by systemd&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;notify-failure@%n&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;# Convert our list of services into a systemd-compatible attribute set&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# This adds failure monitoring to each service in monitoredServices&lt;/span&gt;
  monitoringConfigs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;builtins&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listToAttrs&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt; monitorConfig monitoredServices&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# Import common configuration shared across all machines&lt;/span&gt;
   imports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
     &lt;span class=&quot;token url&quot;&gt;../../base.nix&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;# BOOT CONFIGURATION&lt;/span&gt;

   boot&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;grub &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     enable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     device &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/dev/xvda&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     efiSupport &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;# FIREWALL CONFIGURATION&lt;/span&gt;

   networking&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;firewall &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     enable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

     &lt;span class=&quot;token comment&quot;&gt;# Open ports for web traffic&lt;/span&gt;
     allowedTCPPorts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
       &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# HTTP&lt;/span&gt;
       &lt;span class=&quot;token number&quot;&gt;443&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# HTTPS&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

     &lt;span class=&quot;token comment&quot;&gt;# Custom iptables rules to restrict SSH access to specific IP&lt;/span&gt;
     extraCommands &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;
       # First, block all SSH connections by default
       iptables -A INPUT -p tcp --dport 22 -j DROP

       # Then, allow SSH only from this specific IP address
       iptables -I INPUT &#92;
         -p tcp &#92;
         --dport 22 &#92;
         -s HOME_IP/32 &#92;
         -j ACCEPT
     &#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

     &lt;span class=&quot;token comment&quot;&gt;# Remove our custom rules when the firewall stops&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;# The &#39;|| true&#39; ensures the script doesn&#39;t fail if rules don&#39;t exist&lt;/span&gt;
     extraStopCommands &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;
       iptables -D INPUT -p tcp --dport 22 -j DROP || true
       iptables -D INPUT &#92;
         -p tcp &#92;
         --dport 22 &#92;
         -s HOME_IP/32 &#92;
         -j ACCEPT || true
     &#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;# Caddy web server configuration&lt;/span&gt;
  services&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;caddy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    enable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    virtualHosts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;# Configuration for git.chobble.com&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;git.chobble.com&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# Listen on all interfaces&lt;/span&gt;
        listenAddresses &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;0.0.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;# Reverse proxy configuration for Forgejo&lt;/span&gt;
        extraConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;
          reverse_proxy :3000 {
            header_up Host 127.0.0.1
          }
        &#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;# Forgejo (git hosting) configuration&lt;/span&gt;
  services&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;forgejo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    enable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    settings &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      ui &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        DEFAULT_THEME &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;forgejo-dark&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      server &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        DOMAIN &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;git.chobble.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        ROOT_URL &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://git.chobble.com/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        LANDING_PAGE &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/chobble&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        HTTP_PORT &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;# Prevent new user registrations&lt;/span&gt;
      service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DISABLE_REGISTRATION &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;# Disable Forgejo Actions&lt;/span&gt;
      actions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ENABLED &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;# Systemd services configuration&lt;/span&gt;
  systemd&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;services &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mkMerge &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;# Template service for failure notifications&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;notify-failure@&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        enable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        description &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Failure notification for %i&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        scriptArgs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;%i&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# Pass the service name as an argument&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# Send notification via ntfy.sh when a service fails&lt;/span&gt;
        script &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;${pkgs.curl}/bin/curl &#92;
          --fail &#92;
          --show-error --silent &#92;
          --max-time 10 &#92;
          --retry 3 &#92;
          --data &quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;networking&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hostName&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; service &#39;$1&#39; exited with errors&quot; &#92;
          https://ntfy.sh/MY_NTFY_CHANNEL&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;# Test service that always fails (for testing)&lt;/span&gt;
      always&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;fails &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        description &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Always fails&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        script &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;exit 1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        serviceConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;oneshot&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# Merge in the failure monitoring configurations&lt;/span&gt;
    monitoringConfigs
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;# SSH server configuration&lt;/span&gt;
  services&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;openssh &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    enable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    allowSFTP &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    settings &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      PermitRootLogin &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;no&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      PasswordAuthentication &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    extraConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;
      AllowTcpForwarding yes
      X11Forwarding no
      AllowAgentForwarding no
      AllowStreamLocalForwarding no
      AuthenticationMethods publickey
    &#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;# Remove default packages&lt;/span&gt;
  environment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;defaultPackages &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;# Set home directory permissions&lt;/span&gt;
  users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;homeMode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0777&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  system&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stateVersion &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;23.05&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;flake-nix&quot;&gt;flake.nix&lt;/h2&gt;
&lt;p&gt;And here&#39;s my &lt;code&gt;flake.nix&lt;/code&gt; which includes the above file in &lt;code&gt;./hosts/chobble/settings.nix&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-nix&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-nix&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  description &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Machine Flakes&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  inputs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    nixpkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;github:NixOS/nixpkgs/nixos-24.11&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    nixpkgs&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;unstable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;github:NixOS/nixpkgs/nixos-unstable&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    home&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;manager &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;github:nix-community/home-manager/release-24.11&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      inputs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nixpkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;follows &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;nixpkgs&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    nix&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;flatpak&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;github:gmodena/nix-flatpak&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    nixos&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;hardware&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;github:nixos/nixos-hardware&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    fw&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;fanctrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;github:TamtamHero/fw-fanctrl/packaging/nix&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      inputs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nixpkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;follows &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;nixpkgs&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    site&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;builder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;git+https://git.chobble.com/chobble/nixos-site-builder&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      inputs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nixpkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;follows &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;nixpkgs&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  outputs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; inputs&lt;span class=&quot;token operator&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    nixpkgs&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    nixpkgs&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;unstable&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    home&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;manager&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    nix&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;flatpak&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    nixos&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;hardware&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    fw&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;fanctrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    site&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;builder&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt;
      system &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;x86_64-linux&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      overlay&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;unstable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; final&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; prev&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        unstable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;import&lt;/span&gt; nixpkgs&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;unstable &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;inherit&lt;/span&gt; system&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;allowUnfree &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      mkHost &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        hostname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        extraModules &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nixpkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nixosSystem &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;inherit&lt;/span&gt; system&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        specialArgs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;inherit&lt;/span&gt; inputs&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        modules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;nixpkgs&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;/nixos/modules/installer/scan/not-detected.nix&quot;&lt;/span&gt;

          &lt;span class=&quot;token comment&quot;&gt;# Add the overlay to make unstable packages available&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; nixpkgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;overlays &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; overlay&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;unstable &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

          home&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;manager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nixosModules&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;home&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;manager

          &lt;span class=&quot;token url&quot;&gt;./hosts/&lt;/span&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;hostname&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token url&quot;&gt;/hardware.nix&lt;/span&gt;
          &lt;span class=&quot;token url&quot;&gt;./hosts/&lt;/span&gt;&lt;span class=&quot;token antiquotation important&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;hostname&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token url&quot;&gt;/settings.nix&lt;/span&gt;

          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            home&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;manager &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              useGlobalPkgs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              useUserPackages &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            networking&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hostName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hostname&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt; extraModules&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      nixosConfigurations &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        stef &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mkHost &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          hostname &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;stef&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          extraModules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
            nix&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;flatpak&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nixosModules&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nix&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;flatpak
            nixos&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;hardware&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nixosModules&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;common&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;gpu&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;amd
          &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        framework &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mkHost &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          hostname &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;framework&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          extraModules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
            fw&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;fanctrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nixosModules&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;default
            nix&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;flatpak&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nixosModules&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nix&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;flatpak
            nixos&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;hardware&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nixosModules&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;framework&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7040&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;amd
          &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        latitude &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mkHost &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          hostname &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;latitude&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          extraModules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
            nix&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;flatpak&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nixosModules&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nix&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;flatpak
            nixos&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;hardware&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nixosModules&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dell&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;latitude&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7280&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        nuc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mkHost &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          hostname &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;nuc&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          extraModules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
            nix&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;flatpak&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nixosModules&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nix&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;flatpak
          &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        chobble &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mkHost &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          hostname &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;chobble&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          extraModules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              imports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; site&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nixosModules&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;default &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              services&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;site&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;builder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                enable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                sites &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                  &lt;span class=&quot;token string&quot;&gt;&quot;chobble.com&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    gitRepo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:3000/chobble/chobble&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    wwwRedirect &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                  &lt;span class=&quot;token string&quot;&gt;&quot;veganprestwich.co.uk&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    gitRepo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:3000/chobble/vegan-prestwich&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    wwwRedirect &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                  &lt;span class=&quot;token string&quot;&gt;&quot;blog.chobble.com&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    gitRepo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:3000/chobble/blog&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    wwwRedirect &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;I was tempted to tidy the &lt;code&gt;flake.nix&lt;/code&gt; file up to just the most relevent bits but I think it&#39;s probably handier to see how the Chobble server fits alongside my other machines.&lt;/p&gt;
&lt;p&gt;Long term I want to add this all to a publicly viewable Git repository like everything else, but before I do that I need to strip out any secrets and start using a secret manager. So that&#39;s probably next!&lt;/p&gt;
</content>
  </entry>
</feed>