<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Pixelite]]></title><description><![CDATA[A technology blog or rant area depending on the topic]]></description><link>https://www.pixelite.co.nz/</link><image><url>https://www.pixelite.co.nz/favicon.png</url><title>Pixelite</title><link>https://www.pixelite.co.nz/</link></image><generator>Ghost 4.48</generator><lastBuildDate>Mon, 13 Jul 2026 12:40:53 GMT</lastBuildDate><atom:link href="https://www.pixelite.co.nz/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Bots, scrapers, and proxies: defending Drupal sites in an automated internet]]></title><description><![CDATA[Bots are over half of web traffic. Here is the layered defence I use to keep Drupal sites running - companion to my DrupalSouth Wellington 2026 talk.]]></description><link>https://www.pixelite.co.nz/article/bots-scrapers-and-proxies-defending-drupal-sites-in-an-automated-internet/</link><guid isPermaLink="false">6a091a003e6c5e00071c0341</guid><category><![CDATA[Drupal]]></category><category><![CDATA[Drupal planet]]></category><category><![CDATA[security]]></category><category><![CDATA[Bot management]]></category><category><![CDATA[Cloudflare]]></category><category><![CDATA[WAF]]></category><dc:creator><![CDATA[Sean Hamlin]]></dc:creator><pubDate>Sun, 17 May 2026 02:32:56 GMT</pubDate><media:content url="https://www.pixelite.co.nz/content/images/2026/05/feature-bots-scrapers-proxies.svg" medium="image"/><content:encoded><![CDATA[<img src="https://www.pixelite.co.nz/content/images/2026/05/feature-bots-scrapers-proxies.svg" alt="Bots, scrapers, and proxies: defending Drupal sites in an automated internet"><p>Over half of all web traffic in 2024 was automated. That is the headline number from the Imperva 2025 Bad Bot Report, and it is the first time bots have outnumbered humans in more than a decade. Drupal sites sit squarely in that traffic mix, and the old defensive playbook &#x2014; block an IP, ban a user agent, drop a <code>robots.txt</code> entry, lean on Fail2ban &#x2014; does not hold up anymore.</p><p>This is the companion post to my DrupalSouth Wellington 2026 talk, <em>Bots, scrapers, and proxies: defending Drupal sites in an automated internet</em>. The talk walked through the defences I actually use at amazee.io and recommend on client sites. The post covers the same ground, with a bit more room to show config and link out to the projects.</p><h2 id="what-actually-changed">What actually changed</h2><p>The technical context underneath bot defence has shifted in three ways that matter:</p><ul><li><strong>Residential proxy networks</strong>. Scrapers no longer come from a handful of cloud subnets you can block. They route through real consumer IP addresses, often unwittingly donated by free-VPN users or piggy-backed off shady SDKs in mobile apps.</li><li><strong>Headless browsers everywhere</strong>. Playwright and Puppeteer have made it trivial to render JavaScript-heavy pages at scale. A page that needed a real human five years ago can be scraped today by anyone with a laptop.</li><li><strong>AI-driven scraping</strong>. Volume is up sharply because every new LLM needs training data, and there is now a steady drip of new crawlers showing up. Meta&apos;s <code>externalagent</code> is one recent example. There will be more.</li></ul><p>Mimicry is now the baseline, not the edge case. A modern scraper will rotate IPs, randomise user agents, replay realistic TLS fingerprints, and pace itself slowly enough to look like a real user. You cannot rely on signal that lives in one HTTP header.</p><h2 id="the-scale-of-it">The scale of it</h2><p>If this still sounds like a niche problem, the numbers say otherwise.</p><ul><li><strong>51%</strong> - share of web traffic that was automated in 2024, per the Imperva 2025 Bad Bot Report.</li><li><strong>+96%</strong> - year-on-year growth in some popular bot services across Pantheon&apos;s hosting fleet in their July 2025 data.</li><li><strong>1B+</strong> - unique monthly visitors Pantheon sees across its platform, which is the size of dataset those numbers are coming from.</li></ul><p>On the amazee.io platform globally, 13% of incoming requests can be flagged as non-human based on the user agent alone. That is the lazy bots. The actual share of automated traffic is higher once you account for the ones that try to blend in. In absolute terms it adds up to hundreds of millions of requests every month.</p><h2 id="the-goal-is-not-to-block-all-bots">The goal is not to block all bots</h2><p>Before going through the defences, one thing I am careful to say up front, both on stage and here: the goal is not to block all bots. That is unwinnable, and the closer you get to it the more real users you break.</p><p>Search crawlers, RSS readers, uptime monitors, link-preview generators in Slack and iMessage, accessibility tooling - all bots, all wanted. The goal is to reduce abuse where it hurts most, on the endpoints that cost you real money or real performance, while leaving everything else alone.</p><h2 id="drupal-native-defences">Drupal-native defences</h2><p>The defences closest to your application are the smartest. They can see the path, the user, the form, the cache state. They are also the most expensive per blocked request, because every block at this layer has already cost you a full PHP bootstrap.</p><h3 id="perimeter">Perimeter</h3><p>The <a href="https://www.drupal.org/project/perimeter">Perimeter module</a> drops requests matching known-bad patterns: <code>/wp-admin</code>, <code>/.env</code>, <code>xmlrpc.php</code>, all the WordPress scanner noise that hits every Drupal site daily. It is the cheapest win on the list. It will not stop a serious scraper, but it will keep your logs clean and your error rate honest.</p><h3 id="crowdsec-and-abuseipdb">CrowdSec and AbuseIPDB</h3><p><a href="https://www.crowdsec.net/">CrowdSec</a> is a local agent plus a community blocklist. Every site running CrowdSec contributes detected attacks back to a shared signal, and pulls down the latest list of bad actors. It is the closest thing the open-source world has to a distributed reputation system.</p><p><a href="https://www.abuseipdb.com/">AbuseIPDB</a> is a reputation lookup service. You query an IP, you get a confidence score. It is most useful on the forms and login flows where you can afford the latency of an external API call. Both are available as Drupal modules.</p><h3 id="facet-bot-blocker">Facet Bot Blocker</h3><p>If you run Search API with facets, this is the single cheapest huge win available to you. Faceted search URLs are catnip for scrapers: every combination of filters is a new URL, every URL is uncached, every uncached request hits the database. A bot that crawls a faceted listing can take a site down without trying.</p><p>The <a href="https://www.drupal.org/project/facet_bot_blocker">Facet Bot Blocker module</a> acts as a rate limit on requests that include at least one facet in the URL. Configure it to use Redis or memcache for the counter so you are not making the problem worse by hitting the database to record the request. On one of our hosting customers, this one module cut Search API load by more than half.</p><h3 id="form-side-defences">Form-side defences</h3><p>Logins, registrations, password resets and contact forms all need their own treatment, separate from page-level defence:</p><ul><li><a href="https://www.drupal.org/project/honeypot">Honeypot</a> - invisible field plus a time-based check. Cheap, fast, surprisingly effective against the dumb half of form spam.</li><li><a href="https://www.drupal.org/project/antibot">Antibot</a> - requires JavaScript to submit, blocks the bots that do not run JS.</li><li>CAPTCHA, reCAPTCHA, or <a href="https://www.cloudflare.com/products/turnstile/">Cloudflare Turnstile</a> - full challenge. Use the lightest option that works, and ideally only after Honeypot and Antibot have already rejected the easy cases.</li><li><a href="https://www.drupal.org/project/hidden_captcha">Hidden CAPTCHA</a> - bridges the gap when you want a CAPTCHA-style check without the accessibility cost of a visible challenge.</li></ul><h3 id="the-trade-off-the-project-pages-dont-mention">The trade-off the project pages don&apos;t mention</h3><p>Every block at the Drupal layer has already cost you a PHP bootstrap. That is fine when the absolute volume is small. It is not fine when you are eating hundreds of millions of bot requests and bootstrapping PHP for each one. This is why you cannot stop at the application layer.</p><h2 id="web-server-and-infrastructure">Web server and infrastructure</h2><p>One layer out, the web server can drop requests before PHP ever runs. The trade-off flips: you save the bootstrap cost, but you lose access to application context.</p><h3 id="rate-limiting-and-geo-blocking">Rate limiting and geo blocking</h3><p>nginx ships with <code>limit_req_zone</code>, Apache has <code>mod_ratelimit</code>. Both are blunt but effective on volume. A starting point for nginx looks roughly like this:</p><pre><code class="language-nginx">limit_req_zone $binary_remote_addr zone=search:10m rate=10r/m;

location /search {
    limit_req zone=search burst=5 nodelay;
    proxy_pass http://drupal;
}
</code></pre><p>Ten search requests per minute, per IP, with a burst of five. Tune to taste. The <code>$binary_remote_addr</code> key is cheap on memory; a 10MB zone holds around 160,000 IPs.</p><p>Geo blocking is the other infrastructure-level lever. It is pragmatic and occasionally controversial. If your audience is the New Zealand public sector, blocking inbound from regions you do not serve is a defensible call. If your audience is global, it is not. Know your traffic before reaching for it.</p><h3 id="modsecurity-and-the-owasp-crs">ModSecurity and the OWASP CRS</h3><p>ModSecurity with the <a href="https://owasp.org/www-project-modsecurity-core-rule-set/">OWASP Core Rule Set</a> is a proper WAF you can self-host. Once tuned, it is real protection. The tuning is the catch &#x2014; out of the box it will flag Drupal admin actions, file uploads, anything that looks like SQL in a body or a query string. Expect to spend real time pruning rules and adding exceptions for legitimate site behaviour before you stop generating false positives.</p><h3 id="cache-discipline">Cache discipline</h3><p>A request that hits the cache costs you nothing. Whatever else you do, get your cache headers right. Vary on the bits that need to vary, cache aggressively on the bits that do not, and lean on the page cache or the reverse proxy in front of Drupal. The cheapest bot is the one that asks for a page you have already served.</p><p>(shamless plug) you can also use my site Caching Score to review your current caching setup, to see if there is anything better you can be doing.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.cachingscore.com/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Caching Score</div><div class="kg-bookmark-description">Assess how strong the caching capabilities of any given site is.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://www.cachingscore.com/apple-touch-icon.png" alt="Bots, scrapers, and proxies: defending Drupal sites in an automated internet"><span class="kg-bookmark-author">Caching Score</span><span class="kg-bookmark-publisher">sean.hamlinamazee.io</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://www.cachingscore.com/icons/opengraph.jpg" alt="Bots, scrapers, and proxies: defending Drupal sites in an automated internet"></div></a></figure><h3 id="what-this-layer-is-bad-at">What this layer is bad at</h3><p>Rate limits, ModSecurity rules and geo blocks are great at volume and bad at quality. They cannot tell a scraper trickling one request per minute apart from a real user. For that you need either the edge or the application.</p><h2 id="edge-and-paid-bot-management">Edge and paid bot management</h2><p>The edge is where the big vendors live, and it is where you push the cheapest blocks. A scraper rejected by Cloudflare at the network edge never gets to your origin at all.</p><h3 id="cloudflare">Cloudflare</h3><p>The free tier already includes Bot Fight Mode, basic challenges, and Turnstile. For most small-to-medium Drupal sites, this is a good baseline at zero extra cost. The paid Bot Management product adds custom rule logic, JA3 and JA4 TLS fingerprinting, and machine-learning-based bot scoring you can wire into firewall rules. The jump from free to paid is significant in price; the jump in capability is also significant.</p><h3 id="fastly-akamai-and-the-rest">Fastly, Akamai, and the rest</h3><p>Fastly offers the Next-Gen WAF (originally Signal Sciences) with a Bot Management add-on. Akamai sits at the enterprise tier with the most sophisticated fingerprinting available, and a price tag to match. Beyond those, there is AWS WAF with Bot Control, DataDome, HUMAN, and Imperva &#x2014; all credible, all paid, all priced for sites where bot abuse is costing real money.</p><h3 id="the-trade-offs-nobody-puts-on-the-sales-deck">The trade-offs nobody puts on the sales deck</h3><p>Bot Management at the edge solves real problems. It also comes with real costs that the vendor demos skip past:</p><ul><li><strong>Cost</strong>. Bot Management is almost always an add-on to the core WAF subscription, and the pricing escalates fast with traffic.</li><li><strong>Vendor lock-in</strong>. Your rules, your dashboards, your observability all live in the vendor&apos;s UI. Migrating off is painful.</li><li><strong>Accessibility and SEO</strong>. Aggressive challenges break real users, and search bots that fail a challenge will hurt your rankings. Test both before turning anything up.</li><li><strong>Rules live outside your application codebase</strong>. They drift, they are not versioned alongside the code that depends on them, and a rule change can break a feature without any commit to point to.</li><li><strong>False positives are invisible to you</strong>. By default, blocked requests do not reach your logs. You will not know which real users were turned away unless you specifically ask for that signal.</li></ul><h2 id="anubis">Anubis</h2><p>The newest piece in the picture, and the one that has me genuinely interested.</p><h3 id="what-anubis-is">What Anubis is</h3><p><a href="https://anubis.techaro.lol/">Anubis</a> is an open-source reverse proxy (MIT licensed) that sits in front of your site and issues a proof-of-work challenge to clients before letting them through. It was built specifically for the AI scraper era &#x2014; for the case where the scraper is mimicking a real browser well enough that classifying it on signal alone has stopped working.</p><h3 id="why-proof-of-work-not-captcha">Why proof-of-work, not CAPTCHA</h3><p>The interesting move with Anubis is who pays the cost. A real user pays a few hundred milliseconds of CPU once when they first arrive, and never sees it again for the lifetime of the cookie. A scraper hitting you a million times pays the cost a million times.</p><p>That asymmetry is the whole point. CAPTCHAs put the cost on humans (the people who lose patience trying to identify traffic lights). Anubis puts it on whoever is doing the hammering. That is closer to the right shape of the trade.</p><h3 id="where-to-put-it">Where to put it</h3><p>You do not want Anubis in front of your whole site. You want it in front of the endpoints that are expensive and uncacheable. From the talk, my shortlist:</p><ul><li>Search endpoints</li><li>Facet and filter URLs</li><li>Pagination tails - <code>?page=2348</code> is not a real user</li><li>Login, register, password reset</li><li>Spicy forms (contact, anything that triggers an email)</li><li>Authenticated user flows</li><li>Anything expensive and uncacheable</li></ul><p>Static pages stay fast. The cache stays warm. The PoW cost only applies on the routes where it earns its keep.</p><h3 id="but-what-about-googlebot">But what about Googlebot?</h3><p>This is the first question every site owner asks, and the answer is good. Anubis ships with allowlists for known good crawlers, matching IP ranges against the published lists from Google, Bing, and the rest. The allowlist is maintained upstream, which means you need to keep Anubis deployed on a reasonable cadence to pull in the latest changes. New legitimate crawlers do show up.</p><h3 id="demo-site">Demo site</h3><p>You can see Anubis in action with a demo Drupal 11 site I put together, the login form has Anubis in front of it, the homepage does not.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://spicy.seanhamlin.website/user/login"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Log in | Drush Site-Install</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://spicy.seanhamlin.website/core/themes/olivero/favicon.ico" alt="Bots, scrapers, and proxies: defending Drupal sites in an automated internet"><span class="kg-bookmark-author">Drush Site-Install</span></div></div></a></figure><h2 id="putting-the-layers-together">Putting the layers together</h2><p>None of these defences is a silver bullet on its own. Each layer is cheap at one thing and bad at another, and the trick is matching the layer to the threat.</p><figure class="kg-card kg-image-card kg-width-full kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2026/05/bot-defence-layers-2.svg" class="kg-image" alt="Bots, scrapers, and proxies: defending Drupal sites in an automated internet" loading="lazy" width="283" height="150"><figcaption>Layered defence diagram showing requests flowing from clients through Edge/CDN, Anubis, web server, and Drupal, with cost-to-block increasing as you move closer to the application.</figcaption></figure><p>Block the cheap traffic at the edge. Block the lazy bots with rate limits and ModSecurity at the web server. Put Anubis in front of the endpoints that are expensive and uncacheable. Let Drupal-native modules handle the application-aware decisions where you actually need to see the user, the form, or the facet state.</p><h2 id="five-things-to-take-away">Five things to take away</h2><ol><li><strong>No single layer is enough.</strong> Stack them. The edge handles raw volume, the web server handles patterns, and the application is the only thing that can see real user and form context.</li><li><strong>Match the protection to the threat.</strong> A login form needs different defence to a faceted search results page.</li><li><strong>Measure before you defend.</strong> Look at your actual traffic. Find your most-hit uncacheable endpoints. Defend those first.</li><li><strong>Watch accessibility and SEO.</strong> Every challenge you add is a tax on a real user or a real crawler. The cost of false positives is invisible unless you go looking.</li><li><strong>Plan for adversarial improvement.</strong> Whatever you deploy today, the scrapers get a turn next. Pick defences you can iterate on.</li></ol><h2 id="one-last-thing">One last thing</h2><blockquote>You do not need to win the bot war. You just need to make your site a worse target than the next one.</blockquote><hr><p>The <a href="https://bit.ly/dsnz2026">slides from the talk are online too</a>. The recording is here:</p><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/llrhOS4pmT0?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="Bots, Scrapers, Proxies: Defending Drupal Sites in an Automated Internet / Web Tools / Sean Hamlin"></iframe></figure>]]></content:encoded></item><item><title><![CDATA[Chrome extensions are a supply chain risk - build your own instead]]></title><description><![CDATA[JSON Formatter started injecting ads. Chrome extensions are a supply chain risk. I built my own replacement in under an hour with Claude.]]></description><link>https://www.pixelite.co.nz/article/chrome-extensions-are-a-supply-chain-risk-build-your-own-instead/</link><guid isPermaLink="false">69e7ff434678d600069b698c</guid><category><![CDATA[chrome]]></category><category><![CDATA[security]]></category><category><![CDATA[claude-code]]></category><dc:creator><![CDATA[Sean Hamlin]]></dc:creator><pubDate>Tue, 21 Apr 2026 23:21:34 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1590497008432-598f04441de8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDV8fHN1cHBseSUyMGNoYWlufGVufDB8fHx8MTc3NjgxMzY3MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1590497008432-598f04441de8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDV8fHN1cHBseSUyMGNoYWlufGVufDB8fHx8MTc3NjgxMzY3MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Chrome extensions are a supply chain risk - build your own instead"><p>The <a href="https://chromewebstore.google.com/detail/json-formatter/bcjindcccaagfpapjjmafapmmgkkhgoa">JSON Formatter</a> Chrome extension has over 2 million users. I was one of them for years. A few weeks ago it went closed-source, and shortly after, started injecting adware into web pages. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2026/04/json-formatter-json.jpg" class="kg-image" alt="Chrome extensions are a supply chain risk - build your own instead" loading="lazy" width="811" height="621" srcset="https://www.pixelite.co.nz/content/images/size/w600/2026/04/json-formatter-json.jpg 600w, https://www.pixelite.co.nz/content/images/2026/04/json-formatter-json.jpg 811w" sizes="(min-width: 720px) 720px"><figcaption>Adware from the JSON Formatter Chrome Extension</figcaption></figure><p>The extension was modifying the DOM on checkout pages, hijacking click and submit events to overlay donation popups. It was also running geolocation lookups against MaxMind&apos;s GeoIP2 API with a hardcoded key, profiling users by country so that people in high-income regions got more aggressive prompts. <em>For something that was supposed to format JSON</em>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2026/04/image-4.png" class="kg-image" alt="Chrome extensions are a supply chain risk - build your own instead" loading="lazy" width="1172" height="634" srcset="https://www.pixelite.co.nz/content/images/size/w600/2026/04/image-4.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2026/04/image-4.png 1000w, https://www.pixelite.co.nz/content/images/2026/04/image-4.png 1172w" sizes="(min-width: 720px) 720px"><figcaption>The reviews are punishing. My favourite quote is that this extension &quot;inspires odium&quot;.</figcaption></figure><p>This is a supply chain problem, and it keeps happening.</p><h2 id="chrome-extensions-are-a-trust-problem">Chrome extensions are a trust problem</h2><p>The pattern is well documented at this point. A popular extension with millions of installs gets <a href="https://thehackernews.com/2026/03/chrome-extension-turns-malicious-after.html">sold or transferred to a new owner</a>. The new owner pushes an update that adds tracking and ad injection. Sometimes it goes further into outright credential theft. Users get the update automatically and have no idea anything changed.</p><p>The JSON Formatter incident is almost tame by comparison. But it hit home because I had it installed. I trusted it. And I had no real way to know when the ownership changed or the code was modified.</p><h2 id="what-i-did-about-it">What I did about it</h2><p>Around the same time, <a href="https://x.com/levelsio/status/2046248763103015030">Pieter Levels posted about replacing all his Chrome extensions</a> with a single one he built himself using Claude Code. His reasoning was straightforward: if the code is yours, nobody can sell it to an ad company.</p><p>I liked the idea, so I did the same thing.</p><p>I sat down with Claude Code and built a Chrome extension from scratch. The whole thing took under an hour. The result is a single Manifest V3 extension that currently replaces JSON Formatter for me, and it is extensible so new features can be added easily.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2026/04/image-3.png" class="kg-image" alt="Chrome extensions are a supply chain risk - build your own instead" loading="lazy" width="758" height="610" srcset="https://www.pixelite.co.nz/content/images/size/w600/2026/04/image-3.png 600w, https://www.pixelite.co.nz/content/images/2026/04/image-3.png 758w" sizes="(min-width: 720px) 720px"><figcaption>My &quot;Swiss Army Knife&quot; Chrome extension. Replaces 5+ extensions.</figcaption></figure><p>The repo is public: <a href="https://github.com/seanhamlin/chrome-tweaks">github.com/seanhamlin/chrome-tweaks</a> (not that I expect you to run this, but you can see what Claude can make with a little effort).</p><h2 id="why-build-your-own-instead-of-finding-another-extension">Why build your own instead of finding another extension</h2><p>You could swap JSON Formatter for another extension. There are plenty of alternatives. But that misses the point. Every third-party extension you install is a bet that the current maintainer will stay honest, won&apos;t sell it, and won&apos;t get phished. You&apos;re making that bet for every extension, and Chrome will silently auto-update them all.</p><p>Building your own means:</p><ul><li>You control the code. No surprises in the next update.</li><li>You only include the permissions you actually need.</li><li>You can combine several small utilities into one extension instead of running five separate ones with five separate attack surfaces.</li></ul><h2 id="getting-started-with-your-own-extension">Getting started with your own extension</h2><p>If you have Claude (or any decent AI coding tool), this is a good afternoon project. Chrome extensions are well documented by Google, and the structure is small enough that an LLM can generate the whole thing from a prompt.</p><figure class="kg-card kg-code-card"><pre><code>Can u make a chrome extension for chrome called &quot;Tab Tweaks&quot; - this extension will grow in functionality over time, so ensure it is extensible. The current functionality should include:

* Able to inject a request header to every request. The default should be the Fastly debug header `Fastly-Debug:1`. The user should be allowed to edit this, and change it, or add another header.
* A button to scan the site in www.cachingscore.com. The current URL should be URL encoded, and a new tab opened to point to `https://www.cachingscore.com/scan?q=https%3A%2F%2Fwww.example.com%2F&amp;followRedirects=on` (replace the example URL).</code></pre><figcaption>The initial prompt I used to kick things off in Claude. Since has been added to with further prompts.</figcaption></figure><p>Claude generated the manifest, the content script, and the CSS. I reviewed it, tested it, tweaked it, loaded it as an unpacked extension in <code>chrome://extensions</code>, and it worked.</p><h2 id="audit-your-extensions">Audit your extensions</h2><p>Open <code>chrome://extensions</code> right now and look at what you have installed. For each one, ask yourself:</p><ul><li>Do I actually use this?</li><li>Who maintains it? Is it still the original author?</li><li>What permissions does it have?</li><li>Could I replace this with a simpler version?</li></ul><p>You will probably find at least two or three extensions you forgot about. Remove them.</p><p>For the ones you keep, consider whether the functionality is simple enough to replicate yourself. A lot of developer-focused extensions (JSON formatters, cookie viewers) are doing things that a content script or devtools panel can do in under 100 lines.</p><p>My extension is at <a href="https://github.com/seanhamlin/chrome-tweaks">github.com/seanhamlin/chrome-tweaks</a> if you want to see how it is structured, but the real suggestion is to build your own. You will trust it more than anything in the Chrome Web Store.</p>]]></content:encoded></item><item><title><![CDATA[Debugging Docker image sizes - how to inspect them with dive]]></title><description><![CDATA[Have you ever wondered why your docker images are so large, and what can be done to drill down into the layers one by one. Well, you can.]]></description><link>https://www.pixelite.co.nz/article/debugging-docker-image-sizes-how-to-inspect-them-with-dive/</link><guid isPermaLink="false">6753644d7a2f0100075cdd92</guid><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Sean Hamlin]]></dc:creator><pubDate>Fri, 06 Dec 2024 21:11:27 GMT</pubDate><media:content url="https://www.pixelite.co.nz/content/images/2026/05/dive-feature.svg" medium="image"/><content:encoded><![CDATA[<img src="https://www.pixelite.co.nz/content/images/2026/05/dive-feature.svg" alt="Debugging Docker image sizes - how to inspect them with dive"><p>Have you ever wondered why your docker images are so large, and what can be done to drill down into the layers one by one. Well, you can.</p><h2 id="introducing-dive">Introducing Dive</h2><p><code><a href="https://github.com/wagoodman/dive">dive</a></code> is a cool command line tool for inspecting the layers one by one of your docker images.</p><blockquote>A tool for exploring a Docker image, layer contents, and discovering ways to shrink the size of your Docker/OCI image.</blockquote><h2 id="running-dive-on-an-image">Running dive on an image</h2><p>This is simple, and can be run with docker (meta):</p><pre><code class="language-bash">docker run -it --rm -v /var/run/docker.sock:/var/run/docker.sock docker.io/wagoodman/dive:latest &lt;registry&gt;/&lt;repo&gt;@sha256:&lt;hash&gt;</code></pre><h3 id="example-report">Example report</h3><p>Here is a real life docker image, that came in at 1.3GB, which is on the high side of things. Scrolling through the layers I find this one, to which contributes 560MB all by itself.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2024/12/SCR-20241207-jajj-2.png" class="kg-image" alt="Debugging Docker image sizes - how to inspect them with dive" loading="lazy" width="2000" height="659" srcset="https://www.pixelite.co.nz/content/images/size/w600/2024/12/SCR-20241207-jajj-2.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2024/12/SCR-20241207-jajj-2.png 1000w, https://www.pixelite.co.nz/content/images/size/w1600/2024/12/SCR-20241207-jajj-2.png 1600w, https://www.pixelite.co.nz/content/images/size/w2400/2024/12/SCR-20241207-jajj-2.png 2400w" sizes="(min-width: 1200px) 1200px"><figcaption>Example dive report, focusing on a single layer that is 560MB</figcaption></figure><p>The cause jumps out and smacks you in the face, the layer is copying the entire git repo into the image (see the right hand panel, green indicates a file was added in this layer). This folder never really needed in a docker image, and should not be copied if possible. Finding the offending line in the docker file:</p><pre><code class="language-docker"># Copy the rest of the application code
COPY . /app</code></pre><p>So the fix here is to use <code>.dockerignore</code> and add in <code>.git</code> as a line in that file. Simple.</p><p>The use of <code>.dockerignore</code> is covered in more detail on my <a href="https://www.pixelite.co.nz/article/dockerfile-best-practices/">other blog post</a> that deals with making best practice docker images.</p>]]></content:encoded></item><item><title><![CDATA[Analysing JSON logs with OpenObserve]]></title><description><![CDATA[Level up your JSON log analysis with OpenObserve.]]></description><link>https://www.pixelite.co.nz/article/analysing-logs-with-openobserve/</link><guid isPermaLink="false">660f20e4da68ef0007838395</guid><category><![CDATA[Log]]></category><category><![CDATA[Analytics]]></category><category><![CDATA[Bash]]></category><category><![CDATA[Fastly]]></category><category><![CDATA[Sed]]></category><category><![CDATA[Statistics]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Sean Hamlin]]></dc:creator><pubDate>Thu, 04 Apr 2024 22:21:59 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1489176876421-3b720db0fb3d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGxvZ3MlMjBqc29ufGVufDB8fHx8MTcxMjI2OTE1MHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1489176876421-3b720db0fb3d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGxvZ3MlMjBqc29ufGVufDB8fHx8MTcxMjI2OTE1MHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Analysing JSON logs with OpenObserve"><p>In years past, when I wanted to analyse a bunch of JSON logs, I would use <code>jq</code>. <code>jq</code> is an amazing tool, but suffers from some pretty massive drawbacks for large log analysis:</p><ul><li><code>jq</code> is slow. It is single threaded. Parsing a several GB file will take minutes</li><li>If you want to run multiple different reports, then you need <code>jq</code> to parse it all again. So the first point becomes even more painful</li></ul><p>Enter <a href="https://github.com/openobserve/openobserve">OpenObserve</a> (O2). O2 is an awesome, open source (GPLv3), all in one application that is able to ingest vast quantities of logs (and other things) and be able to query them extremely quickly, and even draw visualisations and dashboards.</p><p>Think of O2 like an extremely lightweight ElasticSearch.</p><p>In my particular case, I often need to visualise JSON logs that come from Fastly. These logs are vast in nature, and being able to analyse millions of logs quickly is key.</p><figure class="kg-card kg-image-card kg-width-full kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2024/04/SCR-20240405-kadv.png" class="kg-image" alt="Analysing JSON logs with OpenObserve" loading="lazy" width="2000" height="562" srcset="https://www.pixelite.co.nz/content/images/size/w600/2024/04/SCR-20240405-kadv.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2024/04/SCR-20240405-kadv.png 1000w, https://www.pixelite.co.nz/content/images/size/w1600/2024/04/SCR-20240405-kadv.png 1600w, https://www.pixelite.co.nz/content/images/size/w2400/2024/04/SCR-20240405-kadv.png 2400w"><figcaption>Example screenshot of the logs feature of O2</figcaption></figure><p>The best thing (IMO) is that you can run it all locally, with a single container, and your computer will not grind to a halt in the process.</p><p>Here is a docker-compose file to get you up and running:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">services:
  openobserve:
    image: public.ecr.aws/zinclabs/openobserve:latest
    restart: unless-stopped
    environment:
      ZO_ROOT_USER_EMAIL: &quot;admin@example.com&quot;
      ZO_ROOT_USER_PASSWORD: &quot;password&quot;
      ZO_INGEST_ALLOWED_UPTO: &quot;48&quot;
      ZO_PAYLOAD_LIMIT: &quot;2000000000&quot;
      ZO_JSON_LIMIT: &quot;2000000000&quot;
    ports:
      - &quot;5080:5080&quot;
    volumes:
      - data:/data
volumes:
  data:
</code></pre><figcaption>docker-compose.yml example</figcaption></figure><p>I had to tweak a few settings (all through environment variables) to allow me to ingest hundreds of MBs in a single payload, and also allow me to ingest data up to 2 days old.</p><p>In order to get my JSON logs into a format that O2 would like, I needed to massage them a little.</p><p>O2 will <a href="https://openobserve.ai/docs/user-guide/concepts/#timestamp">automatically parse timestamps</a> that have the name <code>@timestamp</code>, in my case, the field was just called <code>timestamp</code>. So a little <code>sed</code> to the rescue:</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">sed -i -e &apos;s/&quot;timestamp/&quot;@timestamp/g&apos; $FILE</code></pre><figcaption>Ensure the <code>timestamp</code> field has a <code>@</code> prefixing it</figcaption></figure><p>I then needed to format the JSON lines into a giant JSON array, luckily <a href="https://stackoverflow.com/a/45460092/169882">someone on stackoverflow</a> sorted this:</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">sed -i -e &apos;1s/^/[/; $!s/$/,/; $s/$/]/&apos; $FILE</code></pre><figcaption>Convert a JSON entry per line, into a giant JSON array using <code>sed</code></figcaption></figure><p>Finally, you need to send the formatted JSON array into O2</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">curl -u admin@example.com:password -k http://127.0.0.1:5080/api/default/default/_json --data-binary &quot;@$FILE&quot;</code></pre><figcaption>Ingest the JSON array into a local O2 container</figcaption></figure><p>If it all goes well, then you should see something like:</p><figure class="kg-card kg-code-card"><pre><code class="language-json">{&quot;code&quot;:200,&quot;status&quot;:[{&quot;name&quot;:&quot;default&quot;,&quot;successful&quot;:195001,&quot;failed&quot;:0}]}</code></pre><figcaption>Example command line output from O2 after curl import of JSON</figcaption></figure><p>You can now view the UI at http://localhost:5080/ and start to use the power of O2.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2024/04/image--9-.png" class="kg-image" alt="Analysing JSON logs with OpenObserve" loading="lazy" width="2000" height="1126" srcset="https://www.pixelite.co.nz/content/images/size/w600/2024/04/image--9-.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2024/04/image--9-.png 1000w, https://www.pixelite.co.nz/content/images/size/w1600/2024/04/image--9-.png 1600w, https://www.pixelite.co.nz/content/images/size/w2400/2024/04/image--9-.png 2400w" sizes="(min-width: 1200px) 1200px"><figcaption>Example dashboard showing off 2 visualisations</figcaption></figure><p>Happy graphing and log visualising.</p>]]></content:encoded></item><item><title><![CDATA[Drupal and the Open Web in the Australian Government - 2024 edition]]></title><description><![CDATA[Have you ever wondered how popular Drupal is in your local state and at the Australian Federal Government level? 2024 edition.]]></description><link>https://www.pixelite.co.nz/article/drupal-and-the-open-web-in-the-australian-government-2024-edition/</link><guid isPermaLink="false">65ff96f7004d620006ac3ff4</guid><category><![CDATA[Drupal]]></category><category><![CDATA[Drupal planet]]></category><category><![CDATA[Statistics]]></category><dc:creator><![CDATA[Sean Hamlin]]></dc:creator><pubDate>Sat, 30 Mar 2024 07:09:13 GMT</pubDate><media:content url="https://www.pixelite.co.nz/content/images/2024/03/Drupal-and-the-Open-Web-in-the-Australian-Government---DrupalSouth-2024---NEW-FORMAT.png" medium="image"/><content:encoded><![CDATA[<img src="https://www.pixelite.co.nz/content/images/2024/03/Drupal-and-the-Open-Web-in-the-Australian-Government---DrupalSouth-2024---NEW-FORMAT.png" alt="Drupal and the Open Web in the Australian Government - 2024 edition"><p><em>This is the complementary blog post for my <a href="https://drupalsouth.org/events/drupalsouth-sydney-2024/schedule/3041">DrupalSouth Sydney 2024 session</a>, and also v2.0 follow up of sorts from <a href="https://www.pixelite.co.nz/article/drupal-and-the-open-web-in-the-australian-government-2022/">the original 2022 version</a>. The full presentation was much longer than this blog post, this blog post is just going to highlight the core statistics and findings.</em></p><hr><p>Have you ever wondered how popular Drupal is in your local state and at the Australian Federal Government level? This blog post will help to answer that question, using open source tooling. The hope is that you gain some insight to the relative popularity of Drupal and appreciate more the impact you and Drupal have in Australia.</p><p>As this blog post is a follow up, you can also now start to see trends (data is around 13 months newer than the last time I did this).</p><h2 id="just-show-me-the-graphs">Just show me the graphs</h2><p>Disclaimer:</p><ul><li>This is based on Oct 20, 2023 data</li><li>The scoring is based off PageRank data, so the percentages are not raw counts of websites, but an approximation of how important the respective sites are compared to others (assumes a logarithmic base of 5).</li><li>Wappalyzer detection is not perfect (see the end of this blog post for upstreamed PRs), and there is still a fairly large portion of sites where the CMS cannot be identified</li><li>MoGs make this tricky (PageRank relies on incoming links, which break due to MoGs)</li><li>Only source <code>*.gov.au</code> domains considered (some Government sites use other TLDs)</li><li>Unlikely newly created websites are in the top 10 million just yet (due to how the algorithm of PageRank works)</li></ul><h3 id="all-sites-govau">All sites (*.gov.au)</h3><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2024/03/all1.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2024 edition" loading="lazy" width="1003" height="620"><figcaption>All sites (*.gov.au)</figcaption></figure><h3 id="federal-sites-not-state-based-domains">Federal sites (not state based domains)</h3><p>Programmes like <a href="https://www.govcms.gov.au/">GovCMS</a> are having an impact here. Also interesting that if you are not using Drupal, the chances are you have written something entirely custom.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2024/03/fed1.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2024 edition" loading="lazy" width="1003" height="620"><figcaption>Federal sites (every non-state based domain)</figcaption></figure><h3 id="victoria-vicgovau">Victoria <code>*.vic.gov.au</code></h3><p>The <a href="https://www.vic.gov.au/single-digital-presence">Single Digital Presence (SDP)</a> programme makes a mark in Victoria.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2024/03/vic1.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2024 edition" loading="lazy" width="996" height="616"><figcaption>Victoria (*.vic.gov.au)</figcaption></figure><h3 id="new-south-wales-nswgovau">New South Wales <code>*.nsw.gov.au</code></h3><p>Large Drupal sites like <a href="https://www.nsw.gov.au/">https://www.nsw.gov.au/</a> and <a href="https://www.service.nsw.gov.au/">https://www.service.nsw.gov.au/</a> help to make Drupal dominant in NSW.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2024/03/nsw1.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2024 edition" loading="lazy" width="996" height="616"><figcaption>New South Wales (*.nsw.gov.au)</figcaption></figure><h3 id="south-australia-sagovau">South Australia <code>*.sa.gov.au</code></h3><p>Squiz Matrix increasing in market share &#x2191;5.4% over 2022. There is a clear state led mandate here.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2024/03/sa1.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2024 edition" loading="lazy" width="1000" height="618"><figcaption>South Australia (*.sa.gov.au)</figcaption></figure><h3 id="western-australia-wagovau">Western Australia <code>*.wa.gov.au</code></h3><p>A lot of sites this time around are now identified (decrease of &#x2193;30.8% of unknown sites). Drupal also increased market share by &#x2191;9.9%. </p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2024/03/wa1.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2024 edition" loading="lazy" width="1000" height="618"><figcaption>Western Australia (*.wa.gov.au)</figcaption></figure><h3 id="tasmania-tasgovau">Tasmania <code>*.tas.gov.au</code></h3><p>The lowest usage of Drupal for any Australian state or territory and the highest percentage of Wordpress.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2024/03/tas1.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2024 edition" loading="lazy" width="1000" height="618"><figcaption>Tasmania (*.tas.gov.au)</figcaption></figure><h3 id="queensland-qldgovau">Queensland <code>*.qld.gov.au</code></h3><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2024/03/qld1.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2024 edition" loading="lazy" width="1000" height="618"><figcaption>Queensland (*.qld.gov.au)</figcaption></figure><h3 id="australian-capital-territory-actgovau">Australian Capital Territory <code>*.act.gov.au</code></h3><p>The highest percentage of Squiz compared to any other Australia state or territory.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2024/03/act1.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2024 edition" loading="lazy" width="1000" height="618"><figcaption>Australian Capital Territory (*.act.gov.au)</figcaption></figure><h3 id="northern-territory-ntgovau">Northern Territory <code>*.nt.gov.au</code></h3><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2024/03/nt1.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2024 edition" loading="lazy" width="1000" height="618"><figcaption>Northern Territory (*.nt.gov.au)</figcaption></figure><h3 id="open-source-software-oss-cms-vs-proprietary-cms">Open Source Software (OSS) CMS vs Proprietary CMS</h3><p>For the CMS&apos; that can be identified, splitting them into 2 categories, <a href="https://en.wikipedia.org/wiki/Open-source_software">OSS</a> and <a href="https://en.wikipedia.org/wiki/Proprietary_software">Proprietary</a>. OSS is determined on whether the source code is freely available, and there is a licence that allows me to run it without paying someone.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2024/03/oss1.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2024 edition" loading="lazy" width="986" height="609"><figcaption>Open Source Software (OSS) CMS vs Proprietary CMS</figcaption></figure><h3 id="drupal-sites-by-major-version">Drupal sites by major version</h3><p>For sites reporting as Drupal, Drupal 10 is the most popular. Still 5.4% of Drupal sites running Drupal 7 to which will be End-of-Life (EOL) in <a href="https://www.drupal.org/about/drupal-7/d7eol/partners">early 2025</a>.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2024/03/d1.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2024 edition" loading="lazy" width="986" height="609"><figcaption>Drupal by major version</figcaption></figure><h2 id="score-by-state-and-territory">Score by state and territory</h2><p>This is weighted by total score, broken down by federal/state/territory.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2024/03/state1.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2024 edition" loading="lazy" width="889" height="550"><figcaption>Scores by federal/state/territory in Australia</figcaption></figure><h2 id="observations-and-other-unusual-findings">Observations and other unusual findings</h2><h3 id="drupal-usage">Drupal usage</h3><blockquote class="kg-blockquote-alt">&#x201C;Drupal powers <strong>~29.9%</strong> of all digital experiences that you use in the Australian government. This is<strong>&#x2191;2.7%</strong> compared to 2022&#x201D;</blockquote><h3 id="drupal-growth">Drupal Growth</h3><blockquote class="kg-blockquote-alt">&#x201C;Relative to the growth of Australian government sites, Drupal adoption is growing faster&#x201D;</blockquote><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2024/03/SCR-20240330-lrpy.png" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2024 edition" loading="lazy" width="2000" height="267" srcset="https://www.pixelite.co.nz/content/images/size/w600/2024/03/SCR-20240330-lrpy.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2024/03/SCR-20240330-lrpy.png 1000w, https://www.pixelite.co.nz/content/images/size/w1600/2024/03/SCR-20240330-lrpy.png 1600w, https://www.pixelite.co.nz/content/images/2024/03/SCR-20240330-lrpy.png 2050w" sizes="(min-width: 1200px) 1200px"><figcaption>Drupal adoption is rising faster that Australian government sites are rising</figcaption></figure><h3 id="top-contender">Top contender</h3><blockquote class="kg-blockquote-alt">&#x201C;Squiz Matrix is the top contender with <strong>15.6%</strong>, and has a clear state lead mandate in 5 states/territories. This is<strong>&#x2191;3.5%</strong> compared to 2022&#x201D;</blockquote><h3 id="drupal-7-usage">Drupal 7 usage</h3><blockquote class="kg-blockquote-alt">&#x201C;Drupal 7 usage dropped from 15.8% in 2022 to 5.4% in 2024. This is&#x2193;65.8% compared to 2022. Most popular Drupal 7 site is https://www.sl.nsw.gov.au/&#x201D;</blockquote><p>Also after my presentation I got to meet the team behind the State Library of NSW, and they advised that they are due to upgrade to Drupal 10 anytime soon.</p><h3 id="tls-coverage-is-still-not-100">TLS coverage is still not 100%</h3><p>83 sites with HTTP only (a drop of &#x2193;46 since 2022)</p><!--kg-card-begin: html--><table style="border:none;border-collapse:collapse"><colgroup><col width="374px"><col width="183px"><col width="162px"><col width="120px"></colgroup><tbody><tr style="height:46px"><td style="background-color:#0F0926;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-left: 13.5pt;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:16pt;font-family:Poppins,sans-serif;color:#F2FAFD;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Domain</span></p></td><td style="background-color:#0F0926;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:16pt;font-family:Poppins,sans-serif;color:#F2FAFD;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">CMS</span></p></td><td style="background-color:#0F0926;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 12.7297pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:16pt;font-family:Poppins,sans-serif;color:#F2FAFD;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Page Rank</span></p></td><td style="background-color:#0F0926;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 13.8072pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:16pt;font-family:Poppins,sans-serif;color:#F2FAFD;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Score</span></p></td></tr><tr style="height:30px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><a href="http://www.bom.gov.au/" style="text-decoration:none;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:underline;-webkit-text-decoration-skip:none;text-decoration-skip-ink:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">http://www.bom.gov.au/</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">unknown</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">5.63</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">8614</span></p></td></tr><tr style="height:30px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><a href="http://ips.gov.au/mailman/listinfo" style="text-decoration:none;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:underline;-webkit-text-decoration-skip:none;text-decoration-skip-ink:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">http://ips.gov.au/mailman/listinfo</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">unknown</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">4.68</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1867</span></p></td></tr><tr style="height:30px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><a href="http://www.nntt.gov.au/Pages/Home-Page.aspx" style="text-decoration:none;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:underline;-webkit-text-decoration-skip:none;text-decoration-skip-ink:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">http://www.nntt.gov.au/Pages/Home-Page.aspx</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">microsoft-sharepoint</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">4.68</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1867</span></p></td></tr><tr style="height:30px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><a href="http://ajrp.awm.gov.au/" style="text-decoration:none;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:underline;-webkit-text-decoration-skip:none;text-decoration-skip-ink:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">http://ajrp.awm.gov.au/</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">hcl-notes</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">4.6</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1642</span></p></td></tr><tr style="height:30px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><a href="http://services.land.vic.gov.au/" style="text-decoration:none;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:underline;-webkit-text-decoration-skip:none;text-decoration-skip-ink:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">http://services.land.vic.gov.au/</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">unknown</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">4.56</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1539</span></p></td></tr></tbody></table><!--kg-card-end: html--><h3 id="if-in-doubt-add-a-number">If in doubt, add a number</h3><p>14 sites with <code>ww[0-9]</code> in the domain name (a drop of &#x2193;5 since 2022)</p><!--kg-card-begin: html--><table style="border:none;border-collapse:collapse"><colgroup><col width="374px"><col width="183px"><col width="162px"><col width="120px"></colgroup><tbody><tr style="height:46px"><td style="background-color:#0F0926;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-left: 13.5pt;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:16pt;font-family:Poppins,sans-serif;color:#F2FAFD;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Domain</span></p></td><td style="background-color:#0F0926;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:16pt;font-family:Poppins,sans-serif;color:#F2FAFD;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">CMS</span></p></td><td style="background-color:#0F0926;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 12.7297pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:16pt;font-family:Poppins,sans-serif;color:#F2FAFD;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Page Rank</span></p></td><td style="background-color:#0F0926;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 13.8072pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:16pt;font-family:Poppins,sans-serif;color:#F2FAFD;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Score</span></p></td></tr><tr style="height:30px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><a href="https://www2.gbrmpa.gov.au/" style="text-decoration:none;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:underline;-webkit-text-decoration-skip:none;text-decoration-skip-ink:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">https://www2.gbrmpa.gov.au/</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">drupal</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">5.3</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">5065</span></p></td></tr><tr style="height:30px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><a href="https://www1.health.gov.au/" style="text-decoration:none;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:underline;-webkit-text-decoration-skip:none;text-decoration-skip-ink:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">https://www1.health.gov.au/</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">unknown</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">4.99</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">3075</span></p></td></tr><tr style="height:30px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><a href="https://www9.health.gov.au/" style="text-decoration:none;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:underline;-webkit-text-decoration-skip:none;text-decoration-skip-ink:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">https://www9.health.gov.au/</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">unknown</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">4.59</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1615</span></p></td></tr><tr style="height:30px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><a href="https://www1.aiatsis.gov.au/" style="text-decoration:none;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:underline;-webkit-text-decoration-skip:none;text-decoration-skip-ink:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">https://www1.aiatsis.gov.au/</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">unknown</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">4.53</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1467</span></p></td></tr><tr style="height:30px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><a href="https://www2.sl.nsw.gov.au/" style="text-decoration:none;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:underline;-webkit-text-decoration-skip:none;text-decoration-skip-ink:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">https://www2.sl.nsw.gov.au/</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">unknown</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">4.51</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Poppins,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1420</span></p></td></tr></tbody></table><!--kg-card-end: html--><p>I think this is often used like a form of poor mans version control, often archiving the previous version of the site. For some reason it is archived publicly.</p><h3 id="i-want-the-raw-data">I want the raw data!</h3><p>If you want to make your own visualisations of the data, or even just do random queries &#x201C;how popular is Spark CMS in Western Australia&#x201D;, you can download a CSV from <a href="https://bit.ly/dsau2024csv">https://bit.ly/dsau2024csv</a>. Slides are <a href="https://bit.ly/dsau2024">https://bit.ly/dsau2024</a>.</p><h3 id="upstreamed-enhancements">Upstreamed enhancements</h3><p>I found a lot of software running in certain states, so I upstreamed some changes to better detect these software packages:</p><ul><li>Better SilverStripe detection <a href="https://github.com/enthec/webappanalyzer/pull/120">#120</a></li><li>Datascape detection <a href="https://github.com/enthec/webappanalyzer/pull/122">#122</a></li><li>Spark CMS detection <a href="https://github.com/enthec/webappanalyzer/pull/123">#123</a></li><li>Jadu detection <a href="https://github.com/enthec/webappanalyzer/pull/126">#126</a></li><li>Engagement HQ detection <a href="https://github.com/enthec/webappanalyzer/pull/124">#124</a></li><li>Social Pinpoint detection <a href="https://github.com/enthec/webappanalyzer/pull/125">#125</a></li><li>Citizen Space detection <a href="https://github.com/enthec/webappanalyzer/pull/121">#121</a></li></ul><h2 id="comments">Comments</h2><p>I am keen to hear feedback on this data, and what can be done to improve the scoring. Also, if you can help fill in some of the &apos;unknown&apos; data, let me know, I am happy to craft another PR into WebAppAnalyzer.</p>]]></content:encoded></item><item><title><![CDATA[Finding and deleting large files in a git repository]]></title><description><![CDATA[Large files can slow down cloning and fetching operations, and make your developers less efficient. Use this guide to find and delete those files]]></description><link>https://www.pixelite.co.nz/article/finding-and-deleting-large-files-in-a-git-repo/</link><guid isPermaLink="false">655faeafbd302700075b0447</guid><category><![CDATA[Git]]></category><category><![CDATA[Development]]></category><dc:creator><![CDATA[Sean Hamlin]]></dc:creator><pubDate>Thu, 23 Nov 2023 23:41:21 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1556075798-4825dfaaf498?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGdpdHxlbnwwfHx8fDE3MDA3ODI1Nzd8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<h2 id="why-is-it-important-to-have-small-git-repositories">Why is it important to have small git repositories?</h2><img src="https://images.unsplash.com/photo-1556075798-4825dfaaf498?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGdpdHxlbnwwfHx8fDE3MDA3ODI1Nzd8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Finding and deleting large files in a git repository"><p><a href="https://docs.github.com/en/repositories/working-with-files/managing-large-files/about-large-files-on-github#repository-size-limits">GitHub recommends</a> that git repositories be &lt; 1 GiB is size. The most important reasons why I think this matters:</p><ul><li>It takes longer to clone and fetch larger git repositories. This may impact the time for Continuous Integration (CI) jobs to run, also it may impact the speed of deployments to your hosting infrastructure. It will definitely slow down developers looking to get setup for the first time.</li><li>It adds to space needed to house the git repository. Both GitHub and GitLab will reach out to you if you are storing too much data. Not to mention the lost GB on your laptop.</li></ul><h2 id="find-the-current-size-of-your-git-repo">Find the current size of your git repo</h2><p>There is a command built in to git (<a href="https://github.com/git/git/blob/master/Documentation/RelNotes/1.8.3.txt#L82-L83">since 2013</a>) that does this very easily:</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">$ git count-objects -vH

count: 0
size: 0 bytes
in-pack: 4024
packs: 1
size-pack: 4.27 GiB
prune-packable: 0
garbage: 0
size-garbage: 0 bytes</code></pre><figcaption>Here you can see the size of the git repo is &gt; 4 GiB &#x1F631;</figcaption></figure><h2 id="find-the-large-files-in-your-git-repository">Find the large files in your git repository</h2><p>Now that you know your git repository is excessively large, the next step is to work out why.</p><p>Using this <a href="https://stackoverflow.com/a/42544963/169882">amazing script on stackoverflow</a>:</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">git rev-list --objects --all |
  git cat-file --batch-check=&apos;%(objecttype) %(objectname) %(objectsize) %(rest)&apos; |
  sed -n &apos;s/^blob //p&apos; |
  sort --numeric-sort --key=2 |
  cut -c 1-12,41- |
  $(command -v gnumfmt || echo numfmt) --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest</code></pre><figcaption>List all files in a git repo, sorted by size ascending</figcaption></figure><p><strong>N.B. </strong>on Mac OSX, you will need <code>brew install coreutils</code> for the magic number formatting to work.</p><p>This script will list every single file in your git repository, with the largest files at the bottom, in the example git repository I was looking into, these were the largest files:</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">26aa9bdfd920  564KiB themes/site/hypejs/hypejs.css
1d8f89f0d18a  564KiB themes/site/hypejs/hypejs.css
cf4d7ceb1d4f  684KiB themes/site/css/fonts/fontawesome/fa-brands-400 2.svg
401b7f7b3219  823KiB themes/site/css/fonts/fontawesome/fa-solid-900 2.svg
989b349bb493  4.3GiB files.zip</code></pre><figcaption>Committing a 4.3 GiB file to your git repo will slow down a lot of operations</figcaption></figure><p>You can spot a single large zip file <code>files.zip</code> in the repository, coming in at a whooping 4.3GiB. In this particular case, this was due to a developer accidentally committing the file in one commit, and then thinking they can remove it by deleting it in the next. Git however, remembers data in all commits for all time.</p><p>So how do we actually properly delete <code>files.zip</code> and reclaim that space?</p><h2 id="remove-the-large-files-by-rewriting-git-historyusing-the-bfg-repo-cleaner">Remove the large files by rewriting git history - using the BFG repo cleaner</h2><p>The <a href="https://rtyley.github.io/bfg-repo-cleaner/">BFG repo cleaner</a> is an excellent example of a tool to make complex git operations more approachable for the average punter. I use docker to run it, but if you have <code>java</code> installed locally, then running it natively might be easier for you.</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">docker run -it --rm \
  --volume &quot;$PWD:/home/bfg/workspace&quot; \
  koenrh/bfg \
  --strip-blobs-bigger-than 100M</code></pre><figcaption>Running BFG repo cleaner in a docker container</figcaption></figure><p>The output is amazing from the BFG repo cleaner as well, there is a report supplied showing the files it removed, and which commits needed to be altered.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2023/11/image.png" class="kg-image" alt="Finding and deleting large files in a git repository" loading="lazy" width="1310" height="826" srcset="https://www.pixelite.co.nz/content/images/size/w600/2023/11/image.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2023/11/image.png 1000w, https://www.pixelite.co.nz/content/images/2023/11/image.png 1310w" sizes="(min-width: 720px) 720px"><figcaption>Output of BFG repo cleaner. So fancy.</figcaption></figure><p>You also need to run git garbage collection to reclaim the space</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">  git reflog expire --expire=now --all &amp;&amp; git gc --prune=now --aggressive</code></pre><figcaption>Using <a href="https://git-scm.com/docs/git-gc"><code>git gc</code></a> command to strip out the unwanted dirty data</figcaption></figure><p>After you are all done, re-run the command to find the new size</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">$ git count-objects -vH

count: 0
size: 0 bytes
in-pack: 4024
packs: 1
size-pack: 8.29 MiB
prune-packable: 0
garbage: 0
size-garbage: 0 bytes</code></pre><figcaption>git repo is now 8.29 MiB. Wow. Such small.</figcaption></figure><p>If you are happy then force push over your remote</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">git push</code></pre><figcaption>Push the newly rewritten commits over to your remote.&#xA0;</figcaption></figure><p><strong>N.B.</strong> all rewritten commits will have a different SHA, and all other developers using the same repo will need to do steps to ensure they have the most recent version of the git repo.</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">git fetch origin
git reset --hard origin/master</code></pre><figcaption>Other developers using the same git repo can update their local copies with this command. Or they can reclone if they want (the repo is tiny now).</figcaption></figure><p>Hope this helps somebody out there</p>]]></content:encoded></item><item><title><![CDATA[Listing Fastly users that can access a particular service]]></title><description><![CDATA[I was recently to supply a list of non-superuser user accounts that could access a given Service ID in Fastly, and what their role and permission was. This can be achieved with the Fastly CLI and command line utilities like jq.]]></description><link>https://www.pixelite.co.nz/article/listing-fastly-users-that-can-access-a-particular-service/</link><guid isPermaLink="false">63f6a239c4fd430006a52316</guid><category><![CDATA[Fastly]]></category><category><![CDATA[API]]></category><category><![CDATA[Bash]]></category><dc:creator><![CDATA[Sean Hamlin]]></dc:creator><pubDate>Wed, 22 Feb 2023 23:48:37 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1558732362-f60bf1158d2c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDM3fHxhY2Nlc3N8ZW58MHx8fHwxNjc3MTA5NTY2&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1558732362-f60bf1158d2c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDM3fHxhY2Nlc3N8ZW58MHx8fHwxNjc3MTA5NTY2&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Listing Fastly users that can access a particular service"><p>I was recently to supply a list of non-superuser user accounts that could access a given Service ID in Fastly, and what their role and permission was.</p><p>The Fastly UI is not terribly useful for doing this, if your account is sufficiently large. At present, you would need to enumerate every single user, visit their access controls page, and then review which services they can access, and the permission level granted.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2023/02/image-1.png" class="kg-image" alt="Listing Fastly users that can access a particular service" loading="lazy" width="984" height="110" srcset="https://www.pixelite.co.nz/content/images/size/w600/2023/02/image-1.png 600w, https://www.pixelite.co.nz/content/images/2023/02/image-1.png 984w" sizes="(min-width: 720px) 720px"><figcaption>When you have a non-trivial amount of users and services to review, it can take ages to review</figcaption></figure><p>Users can be granted a range of roles, and within the <code>engineer</code> role can be granted permissions as a service ID level.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2023/02/image.png" class="kg-image" alt="Listing Fastly users that can access a particular service" loading="lazy" width="1292" height="358" srcset="https://www.pixelite.co.nz/content/images/size/w600/2023/02/image.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2023/02/image.png 1000w, https://www.pixelite.co.nz/content/images/2023/02/image.png 1292w" sizes="(min-width: 720px) 720px"><figcaption>Engineer role allows you to delegate fine grained access to specific services.</figcaption></figure><p>Rather than do this by hand (on top of taking loads of time, it would be prone to human error), I wanted to work out how to achieve this through programmatic means.</p><h2 id="step-1setup-fastly-cli">Step 1 - Setup Fastly CLI</h2><p>This tool is invaluable for dealing with Fastly on the command line. <a href="https://developer.fastly.com/reference/cli/#installing">Installation instructions can be found on Fastly&apos;s website</a>.</p><p>I then configure the CLI tool with environment variables in my <code>~/.zshrc</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">FASTLY_API_TOKEN=notarealtokenyo
export FASTLY_API_TOKEN
FASTLY_SERVICE_ID=banana
export FASTLY_SERVICE_ID
FASTLY_CUSTOMER_ID=notarealcustomerid
export FASTLY_CUSTOMER_ID</code></pre><figcaption>Environment variables to configure the Fastly CLI.</figcaption></figure><h2 id="step-2list-service-auth">Step 2 - List service auth</h2><p>This will list all user accounts that have access to a particular service ID <code>$FASTLY_SERVICE_ID</code> :</p><p><strong>N.B. </strong>This does not include users with access to all services (such as <code>superuser</code>).</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">fastly service-auth list --json | jq --arg SERVICE_ID &quot;$FASTLY_SERVICE_ID&quot; -r &apos;.Items[] | select( .Service.ID | contains($SERVICE_ID)) | {id: .User.ID, permission: .Permission, created: .CreatedAt}&apos; | jq -s &apos;.&apos; &gt; auth.json</code></pre><figcaption>List of Fastly service auth for a given service ID</figcaption></figure><p>The output will be similar to:</p><figure class="kg-card kg-code-card"><pre><code class="language-json">[
  {
    &quot;id&quot;: &quot;16Sw3VMRzrXXXXLQfL6aen&quot;,
    &quot;permission&quot;: &quot;purge_all&quot;,
    &quot;created&quot;: &quot;2022-09-20T13:37:43Z&quot;
  },
  {
    &quot;id&quot;: &quot;ZFVW99lVnYXXXXbohCv82&quot;,
    &quot;permission&quot;: &quot;purge_all&quot;,
    &quot;created&quot;: &quot;2022-05-10T23:29:30Z&quot;
  }
]
</code></pre><figcaption>List of Fastly service auth for a given service ID in JSON format</figcaption></figure><h2 id="step-3list-users">Step 3 - List users</h2><p>Having access to only the user ID is not that great, we still need to match this to a users email address</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">fastly user list --customer-id $FASTLY_CUSTOMER_ID --json | jq -r &apos;.[] | {id: .ID, email: .Login, role: .Role, locked: .Locked, tfa: .TwoFactorAuthEnabled}&apos; | jq -s &apos;.&apos; &gt; user.json
</code></pre><figcaption>List users in a given Fastly account</figcaption></figure><p>The output will be similar to:</p><figure class="kg-card kg-code-card"><pre><code class="language-json">[
  {
    &quot;id&quot;: &quot;16Sw3VMRzrXXXXLQfL6aen&quot;,
    &quot;email&quot;: &quot;somebody@example.com&quot;,
    &quot;role&quot;: &quot;engineer&quot;,
    &quot;locked&quot;: false,
    &quot;tfa&quot;: true
  },
  {
    &quot;id&quot;: &quot;ZFVW99lVnYXXXXbohCv82&quot;,
    &quot;email&quot;: &quot;anotherperson@example.com&quot;,
    &quot;role&quot;: &quot;engineer&quot;,
    &quot;locked&quot;: false,
    &quot;tfa&quot;: true
  }
]</code></pre><figcaption>List users in a given Fastly account in JSON format</figcaption></figure><h2 id="step-4joining-the-2-json-files-together">Step 4 - Joining the 2 JSON files together</h2><p>We now have 2 JSON files, that we need to merge, based on a shared key <code>id</code> and it&apos;s value. We can use native <code>JOIN</code> and <code>INDEX</code> in <code>jq</code> to do this (<a href="https://stackoverflow.com/a/71563307/169882">see the stackoverflow post</a>):</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">jq &apos;JOIN(INDEX(inputs[];.id);.[];.id;add)&apos; auth.json user.json | jq -s &apos;.&apos; </code></pre><figcaption>Merging 2 JSON files together with a shared key</figcaption></figure><p>The output is now pretty much perfect</p><figure class="kg-card kg-code-card"><pre><code class="language-json">$ jq &apos;JOIN(INDEX(inputs[];.id);.[];.id;add)&apos; auth.json user.json | jq -s &apos;.&apos;
[
  {
    &quot;id&quot;: &quot;16Sw3VMRzrXXXXLQfL6aen&quot;,
    &quot;permission&quot;: &quot;purge_all&quot;,
    &quot;created&quot;: &quot;2022-09-20T13:37:43Z&quot;,
    &quot;email&quot;: &quot;somebody@example.com&quot;,
    &quot;role&quot;: &quot;engineer&quot;,
    &quot;locked&quot;: false,
    &quot;tfa&quot;: true
  },
  {
    &quot;id&quot;: &quot;ZFVW99lVnYXXXXbohCv82&quot;,
    &quot;permission&quot;: &quot;purge_all&quot;,
    &quot;created&quot;: &quot;2022-05-10T23:29:30Z&quot;,
    &quot;email&quot;: &quot;anotherperson@example.com&quot;,
    &quot;role&quot;: &quot;engineer&quot;,
    &quot;locked&quot;: false,
    &quot;tfa&quot;: true
  }
]</code></pre><figcaption>Merged 2 JSON files together</figcaption></figure><p>You can make a CSV (with header row) with some additional effort (<a href="https://stackoverflow.com/a/32965227/169882">see the stackoverflow post</a>):</p><pre><code class="language-bash">$ jq &apos;JOIN(INDEX(inputs[];.id);.[];.id;add)&apos; auth.json user.json | jq -s &apos;.&apos; | jq -r &apos;(map(keys) | add | unique) as $cols | map(. as $row | $cols | map($row[.])) as $rows | $cols, $rows[] | @csv&apos;
&quot;created&quot;,&quot;email&quot;,&quot;id&quot;,&quot;locked&quot;,&quot;permission&quot;,&quot;role&quot;,&quot;tfa&quot;
&quot;2022-09-20T13:37:43Z&quot;,&quot;somebody@example.com&quot;,&quot;16Sw3VMRzrXXXXLQfL6aen&quot;,false,&quot;purge_all&quot;,&quot;engineer&quot;,true
&quot;2022-05-10T23:29:30Z&quot;,&quot;anotherperson@example.com&quot;,&quot;ZFVW99lVnYXXXXbohCv82&quot;,false,&quot;purge_all&quot;,&quot;engineer&quot;,true</code></pre><p>You could even turn this into a giant single one liner if you want, but I am happy with this being a multi-step process.</p>]]></content:encoded></item><item><title><![CDATA[Drupal and the Open Web in the Australian Government - 2022 edition]]></title><description><![CDATA[Have you ever wondered how popular Drupal is in your local state and at the Australian Federal Government level?]]></description><link>https://www.pixelite.co.nz/article/drupal-and-the-open-web-in-the-australian-government-2022/</link><guid isPermaLink="false">6351dd8fb228110007e518d0</guid><category><![CDATA[Drupal]]></category><category><![CDATA[Drupal planet]]></category><category><![CDATA[Statistics]]></category><dc:creator><![CDATA[Sean Hamlin]]></dc:creator><pubDate>Sun, 13 Nov 2022 22:54:55 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1451187580459-43490279c0fa?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDE3fHxwbGFuZXR8ZW58MHx8fHwxNjY4NDE3MjI1&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1451187580459-43490279c0fa?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDE3fHxwbGFuZXR8ZW58MHx8fHwxNjY4NDE3MjI1&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Drupal and the Open Web in the Australian Government - 2022 edition"><p><em>This is the complementary blog post for my <a href="https://drupalsouth.org/events/drupalsouth-brisbane-2022/schedule/1662">DrupalSouth Brisbane 2022 session</a>.</em></p><p>Have you ever wondered how popular Drupal is in your local state and at the Australian Federal Government level? This blog post will help to answer that question, using open source tooling. The hope is that you gain some insight to the relative popularity of Drupal and appreciate more the impact you and Drupal have in Australia.</p><h2 id="existing-solutions">Existing solutions</h2><p>There are a number of websites that will claim to be able to give you this information. However they all will likely want you at some point to pay them money.</p><ul><li>wappalyzer.com</li><li>semrush.com</li><li>builtwith.com</li><li>whatcms.com</li><li>similartech.com</li><li>larger.io</li></ul><blockquote class="kg-blockquote-alt">&#x201C;I wanted an open way to do this&#x201D;</blockquote><p>As it turns out, you can plug a few things together to scrape the technologies in use</p><ul><li><a href="https://github.com/wappalyzer/wappalyzer ">Wappalyzer library</a> (open source)</li><li><a href="https://github.com/puppeteer/puppeteer">Puppeteer</a> (open source)</li></ul><h2 id="problem-1-how-to-get-a-list-of-all-australia-government-domains">Problem #1: How to get a list of all Australia Government domains</h2><p>If anything, there are too many sources of this information:</p><ul><li><a href="https://github.com/tb0hdan/domains">https://github.com/tb0hdan/domains</a> </li><li><a href="https://crt.sh/?q=gov.au">Let&apos;s Encrypt Certificate Transparency (CT) logs</a></li><li><a href="https://securitytrails.com/list/apex_domain/gov.au">SecurityTrails</a> </li></ul><p>A crawling method could also be done, loads of suitable seed sites, e.g.:</p><ul><li><a href="https://www.australia.gov.au/">https://www.australia.gov.au/</a></li><li><a href="https://www.directory.gov.au/departments-and-agencies">https://www.directory.gov.au/departments-and-agencies</a> </li></ul><p>The main issue is that just having a list of sites, does not convey the <em>importance</em> of the site relative to another site.</p><p>Enter <a href="https://www.domcop.com/top-10-million-websites">DomCop</a> to which publishes a list of the top 10 million domains on the internet, including a rank and Open PageRank. </p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2022/11/top10million.png" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2022 edition" loading="lazy" width="1600" height="823" srcset="https://www.pixelite.co.nz/content/images/size/w600/2022/11/top10million.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2022/11/top10million.png 1000w, https://www.pixelite.co.nz/content/images/2022/11/top10million.png 1600w" sizes="(min-width: 1200px) 1200px"><figcaption>DomCop&apos;s top 10 million websites with a filter of <code>.gov.au</code> applied.</figcaption></figure><p>On top of suppling 5,795 Australian Government domains, there also is an &quot;Open Page Rank&quot; field. The PageRanks are calculated based on the Open data provided by <a href="http://commoncrawl.org/">Common Crawl</a> and <a href="https://about.commonsearch.org/">Common Search</a>.</p><h2 id="problem-2-what-is-pagerank">Problem #2: What is PageRank?</h2><p><a href="https://en.wikipedia.org/wiki/PageRank">PageRank</a> is a system for ranking web pages that Google&apos;s founders developed in 1996. A PageRank score of 0 is typically a low-quality website, whereas, a score of 10 would represent only the most authoritative sites on the web. It is logarithmic (with a base of 5).</p><p>A site with PageRank 3 is 5 times more authoritative than a site with PageRank 2.</p><h2 id="problem-3-machinery-of-government-mog-and-link-rot">Problem #3: Machinery of Government (MoG) and link rot</h2><p>Australian Governments sites are never static, they are constantly evolving. Sometimes several sites merge into 1, or sometimes 1 site splits into move sites.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2022/11/vhs.png" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2022 edition" loading="lazy" width="1600" height="963" srcset="https://www.pixelite.co.nz/content/images/size/w600/2022/11/vhs.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2022/11/vhs.png 1000w, https://www.pixelite.co.nz/content/images/2022/11/vhs.png 1600w" sizes="(min-width: 720px) 720px"><figcaption>DHS Victoria is now closed. 3 sites now replace this 1 site.</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2022/11/dese.png" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2022 edition" loading="lazy" width="1600" height="655" srcset="https://www.pixelite.co.nz/content/images/size/w600/2022/11/dese.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2022/11/dese.png 1000w, https://www.pixelite.co.nz/content/images/2022/11/dese.png 1600w" sizes="(min-width: 720px) 720px"><figcaption>DESE is also now closed. 2 sites replace this 1 site.</figcaption></figure><h2 id="just-show-me-the-graphs">Just show me the graphs</h2><p>Disclaimer:</p><ul><li>This is based on Sept 22, 2022 data</li><li>The scoring is based off PageRank data, so the percentages are not raw counts of websites, but an approximation of how important the respective sites are compared to others (assumes a logarithmic base of 5).</li><li>Wappalyzer detection is not perfect (see the end of this blog post for upstreamed PRs), and there is still a fairly large portion of sites where the CMS cannot be identified</li><li>MoGs make this tricky (PageRank relies on incoming links, which break due to MoGs)</li><li>Only <code>*.gov.au</code> domains considered (some Government sites use other TLDs)</li><li>Unlikely newly created websites are in the top 10 million just yet (due to how PageRank works)</li></ul><h3 id="all-sites-govau">All sites (*.gov.au)</h3><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2022/11/all.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2022 edition" loading="lazy" width="1003" height="620"><figcaption>All sites (*.gov.au)</figcaption></figure><h3 id="federal-sites-not-state-based-domains">Federal sites (not state based domains)</h3><p>Programmes like <a href="https://www.govcms.gov.au/">GovCMS</a> are having an impact here.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2022/11/fed.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2022 edition" loading="lazy" width="1003" height="620"><figcaption>Federal sites (every non-state based domain)</figcaption></figure><h3 id="victoria-vicgovau">Victoria <code>*.vic.gov.au</code></h3><p>The <a href="https://www.vic.gov.au/single-digital-presence">Single Digital Presence (SDP)</a> programme makes a mark in Victoria.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2022/11/vic.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2022 edition" loading="lazy" width="996" height="616"><figcaption>Victoria (*.vic.gov.au)</figcaption></figure><h3 id="new-south-wales-nswgovau">New South Wales <code>*.nsw.gov.au</code></h3><p>Large Drupal sites like <a href="https://www.nsw.gov.au/">https://www.nsw.gov.au/</a> and <a href="https://www.service.nsw.gov.au/">https://www.service.nsw.gov.au/</a> help to make Drupal dominant in NSW.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2022/11/nsw.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2022 edition" loading="lazy" width="996" height="616"><figcaption>New South Wales (*.nsw.gov.au)</figcaption></figure><h3 id="south-australia-sagovau">South Australia <code>*.sa.gov.au</code></h3><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2022/11/sa.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2022 edition" loading="lazy" width="1000" height="618"><figcaption>South Australia (*.sa.gov.au)</figcaption></figure><h3 id="western-australia-wagovau">Western Australia <code>*.wa.gov.au</code></h3><p>A lot of unknown CMSs in WA, <s>including sites like <a href="https://ww2.health.wa.gov.au/">https://ww2.health.wa.gov.au/</a> which I still have no idea what the CMS used is</s>. <strong>Edit </strong>- a keen eyed developer has told me that <a href="https://ww2.health.wa.gov.au/~/media/System/Simulator%20Backgrounds/blackberry.ashx">because this URL exists</a>, so the WA Health site is generated by SiteCore. </p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2022/11/wa.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2022 edition" loading="lazy" width="1000" height="618"><figcaption>Western Australia (*.wa.gov.au)</figcaption></figure><h3 id="tasmania-tasgovau">Tasmania <code>*.tas.gov.au</code></h3><p>The lowest usage of Drupal for any Australian state or territory and the highest percentage of Wordpress.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2022/11/tas.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2022 edition" loading="lazy" width="1000" height="618"><figcaption>Tasmania (*.tas.gov.au)</figcaption></figure><h3 id="queensland-qldgovau">Queensland <code>*.qld.gov.au</code></h3><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2022/11/qld.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2022 edition" loading="lazy" width="1000" height="618"><figcaption>Queensland (*.qld.gov.au)</figcaption></figure><h3 id="australian-capital-territory-actgovau">Australian Capital Territory <code>*.act.gov.au</code></h3><p>The highest percentage of Squiz compared to any other Australia state or territory.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2022/11/act.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2022 edition" loading="lazy" width="1000" height="618"><figcaption>Australian Capital Territory (*.act.gov.au)</figcaption></figure><h3 id="northern-territory-ntgovau">Northern Territory <code>*.nt.gov.au</code></h3><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2022/11/nt.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2022 edition" loading="lazy" width="1000" height="618"><figcaption>Northern Territory (*.nt.gov.au)</figcaption></figure><h3 id="open-source-software-oss-cms-vs-proprietary-cms">Open Source Software (OSS) CMS vs Proprietary CMS</h3><p>For the CMS&apos; that can be identified, splitting them into 2 categories, <a href="https://en.wikipedia.org/wiki/Open-source_software">OSS</a> and <a href="https://en.wikipedia.org/wiki/Proprietary_software">Proprietary</a>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2022/11/oss.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2022 edition" loading="lazy" width="986" height="609"><figcaption>Open Source Software (OSS) CMS vs Proprietary CMS</figcaption></figure><h3 id="drupal-sites-by-major-version">Drupal sites by major version</h3><p>For sites reporting as Drupal, Drupal 9 and 7 are the most popular.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2022/11/drupal.svg" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2022 edition" loading="lazy" width="986" height="609"><figcaption>Drupal by major version</figcaption></figure><p></p><h2 id="observations-and-other-unusual-findings">Observations and other unusual findings</h2><h3 id="1drupal-usage">#1 - Drupal usage</h3><blockquote class="kg-blockquote-alt">&#x201C;Drupal powers roughly <strong>27%</strong> of all digital experiences that you use in the Australian government&#x201D;</blockquote><h3 id="2top-contender">#2 - Top contender</h3><blockquote class="kg-blockquote-alt">&#x201C;Squiz Matrix is the top contender with <strong>15%</strong>, and has a clear state led mandate in certain states/territories&#x201D;</blockquote><h3 id="3tls-coverage">#3 - TLS coverage</h3><p>TLS coverage is not 100% - 129 domains found with no TLS</p><!--kg-card-begin: html--><table style="border:none;border-collapse:collapse"><colgroup><col width="428px"><col width="129px"><col width="162px"><col width="120px"></colgroup><tbody><tr style="height:46px"><td style="background-color:#0F0926;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-left: 13.5pt;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:16pt;font-family:Ubuntu,sans-serif;color:#F2FAFD;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Domain</span></p></td><td style="background-color:#0F0926;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:16pt;font-family:Ubuntu,sans-serif;color:#F2FAFD;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">CMS</span></p></td><td style="background-color:#0F0926;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 12.7297pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:16pt;font-family:Ubuntu,sans-serif;color:#F2FAFD;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Page Rank</span></p></td><td style="background-color:#0F0926;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 13.8072pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:16pt;font-family:Ubuntu,sans-serif;color:#F2FAFD;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Score</span></p></td></tr><tr style="height:30px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-left: 13.5pt;margin-top:0pt;margin-bottom:0pt;"><a href="http://www.bom.gov.au/" style="text-decoration:none;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">http://www.bom.gov.au/</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">unknown</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 12.7297pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">5.51</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 13.8072pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">7,101</span></p></td></tr><tr style="height:30px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-left: 13.5pt;margin-top:0pt;margin-bottom:0pt;"><a href="http://handle.slv.vic.gov.au/" style="text-decoration:none;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">http://handle.slv.vic.gov.au/</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">unknown</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 12.7297pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">4.47</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 13.8072pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1,332</span></p></td></tr><tr style="height:30px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-left: 13.5pt;margin-top:0pt;margin-bottom:0pt;"><a href="http://www.mbsonline.gov.au/internet/mbsonline/publishing.nsf/Content/Home" style="text-decoration:none;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">http://www.mbsonline.gov.au/internet/mbsonline/publishing.nsf/Content/Home</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">hcl-notes</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 12.7297pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">4.45</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 13.8072pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1,289</span></p></td></tr><tr style="height:30px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-left: 13.5pt;margin-top:0pt;margin-bottom:0pt;"><a href="http://onesearch.slq.qld.gov.au/primo-explore/search?vid=SLQ" style="text-decoration:none;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">http://onesearch.slq.qld.gov.au/primo-explore/search?vid=SLQ</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">unknown</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 12.7297pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">4.41</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 13.8072pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1,209</span></p></td></tr><tr style="height:30px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-left: 13.5pt;margin-top:0pt;margin-bottom:0pt;"><a href="http://www.majorprojects.planning.nsw.gov.au/" style="text-decoration:none;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">http://www.majorprojects.planning.nsw.gov.au/</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">unknown</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 12.7297pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">4.41</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 13.8072pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1,209</span></p></td></tr></tbody></table><!--kg-card-end: html--><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2022/10/bom.png" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2022 edition" loading="lazy" width="2000" height="947" srcset="https://www.pixelite.co.nz/content/images/size/w600/2022/10/bom.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2022/10/bom.png 1000w, https://www.pixelite.co.nz/content/images/size/w1600/2022/10/bom.png 1600w, https://www.pixelite.co.nz/content/images/2022/10/bom.png 2128w" sizes="(min-width: 1200px) 1200px"><figcaption>The second most trafficked site in the Australian Government does not support TLS. Instead this awkward redirect page is used. And a sad face emoji. Sad face indeed.</figcaption></figure><h3 id="4if-in-doubt-add-a-number">#4 - If in doubt, add a number</h3><p>19 domains found with <code>ww[number]</code> as a subdomain.</p><!--kg-card-begin: html--><table style="border:none;border-collapse:collapse"><colgroup><col width="419px"><col width="154px"><col width="154px"><col width="154px"></colgroup><tbody><tr style="height:41px"><td style="background-color:#0F0926;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-left: 18pt;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:16pt;font-family:Ubuntu,sans-serif;color:#F2FAFD;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Domain</span></p></td><td style="background-color:#0F0926;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:16pt;font-family:Ubuntu,sans-serif;color:#F2FAFD;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">CMS</span></p></td><td style="background-color:#0F0926;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:13px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:16pt;font-family:Ubuntu,sans-serif;color:#F2FAFD;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Page Rank</span></p></td><td style="background-color:#0F0926;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:19px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:16pt;font-family:Ubuntu,sans-serif;color:#F2FAFD;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Score</span></p></td></tr><tr style="height:34px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-left: 18pt;margin-top:0pt;margin-bottom:0pt;"><a href="https://www2.gbrmpa.gov.au/" style="text-decoration:none;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">https://www2.gbrmpa.gov.au/</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">drupal</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 7.2692pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">4.85</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 11.7692pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">2,455</span></p></td></tr><tr style="height:34px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-left: 18pt;margin-top:0pt;margin-bottom:0pt;"><a href="https://www1.health.gov.au/" style="text-decoration:none;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">https://www1.health.gov.au/</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">unknown</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 7.2692pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">4.62</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 11.7692pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1,695</span></p></td></tr><tr style="height:34px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-left: 18pt;margin-top:0pt;margin-bottom:0pt;"><a href="https://ww2.health.wa.gov.au/" style="text-decoration:none;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">https://ww2.health.wa.gov.au/</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">unknown</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 7.2692pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">4.49</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 11.7692pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1,375</span></p></td></tr><tr style="height:34px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-left: 18pt;margin-top:0pt;margin-bottom:0pt;"><a href="http://www9.health.gov.au/" style="text-decoration:none;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">http://www9.health.gov.au/</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">unknown</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 7.2692pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">4.3</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 11.7692pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1,013</span></p></td></tr><tr style="height:34px"><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-left: 18pt;margin-top:0pt;margin-bottom:0pt;"><a href="https://www0.landgate.wa.gov.au/" style="text-decoration:none;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">https://www0.landgate.wa.gov.au/</span></a></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;text-align: center;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">squiz-matrix</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 7.2692pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">4.25</span></p></td><td style="background-color:#FFFFFF;opacity:1;filter:alpha(opacity=100);vertical-align:bottom;border-left:solid #CCCCCC 1px;border-right:solid #CCCCCC 1px;border-top:solid #CCCCCC 1px;border-bottom:solid #CCCCCC 1px;padding-top:2px;padding-right:3px;padding-bottom:2px;padding-left:3px"><p dir="ltr" style="line-height:1.38;margin-right: 11.7692pt;text-align: right;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:10pt;font-family:Ubuntu,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">935</span></p></td></tr></tbody></table><!--kg-card-end: html--><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2022/10/doh.png" class="kg-image" alt="Drupal and the Open Web in the Australian Government - 2022 edition" loading="lazy" width="1140" height="366" srcset="https://www.pixelite.co.nz/content/images/size/w600/2022/10/doh.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2022/10/doh.png 1000w, https://www.pixelite.co.nz/content/images/2022/10/doh.png 1140w"><figcaption>When you run out of subdomains, just add a number.</figcaption></figure><h3 id="5you-cannot-kill-dreamweaver">#5 - You cannot kill Dreamweaver</h3><p>15 sites found in 2022. </p><h2 id="extending-this-for-the-future">Extending this for the future</h2><ul><li>Crawl other domain spaces, e.g. the New Zealand government domain space <code>*.govt.nz</code></li><li>Make a website and publish this data quarterly (DomCop&apos;s data updates around this frequency)</li><li>Measure trends over time</li></ul><h2 id="upstreamed-enhancements">Upstreamed enhancements</h2><p>These are all to make the detection of CMS&apos; and Javascript frameworks more accurate for Australia Government sites.</p><ul><li>Ripple <a href="https://github.com/wappalyzer/wappalyzer/pull/6827">#6827</a> </li><li>Dreamweaver <a href="https://github.com/wappalyzer/wappalyzer/pull/6828">#6828</a> </li><li>HCL DX <a href="https://github.com/wappalyzer/wappalyzer/pull/6829">#6829</a> </li><li>MODX <a href="https://github.com/wappalyzer/wappalyzer/pull/6832">#6832</a> </li><li>Squiz Matrix <a href="https://github.com/wappalyzer/wappalyzer/pull/6833">#6833</a></li><li>Optimizely CMS <a href="https://github.com/wappalyzer/wappalyzer/pull/6834">#6834</a></li><li>Umbraco <a href="https://github.com/wappalyzer/wappalyzer/pull/6835">#6835</a></li><li>Elcom <a href="https://github.com/wappalyzer/wappalyzer/pull/6836">#6836</a> </li><li>Lagoon <a href="https://github.com/wappalyzer/wappalyzer/pull/6861">#6861</a></li><li>OSS flags and links <a href="https://github.com/wappalyzer/wappalyzer/pull/6902">#6902</a></li><li>Squiz Matrix #2 <a href="https://github.com/wappalyzer/wappalyzer/pull/6925">#6925</a></li><li>Ektron CMS <a href="https://github.com/wappalyzer/wappalyzer/pull/6924">#6924</a></li><li>SiteCore <a href="https://github.com/wappalyzer/wappalyzer/pull/6926">#6926</a></li></ul><h2 id="raw-data">Raw data</h2><p>If you want to do your own analysis, here is a link to a <a href="https://gist.github.com/seanhamlin/077002e9775f3c1bc1027f4ba4d380bd">full CSV</a> dump.</p><h2 id="comments">Comments</h2><p>I am keen to hear feedback on this data, and what can be done to improve the scoring. Also, if you can help fill in some of the &apos;unknown&apos; data, let me know, I am happy to craft another PR into Wappalyzer.</p>]]></content:encoded></item><item><title><![CDATA[Choosing the right Amazon EC2 Spot Instances for your workloads]]></title><description><![CDATA[The main issue was narrowing down the enormous list of instance types so they would be suitable to run our workloads.]]></description><link>https://www.pixelite.co.nz/article/choosing-the-right-spot-instances-for-you/</link><guid isPermaLink="false">630d430c6d4d1400076bf661</guid><category><![CDATA[AWS]]></category><category><![CDATA[FinOps]]></category><category><![CDATA[kubernetes]]></category><dc:creator><![CDATA[Sean Hamlin]]></dc:creator><pubDate>Mon, 29 Aug 2022 23:37:14 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1530124566582-a618bc2615dc?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDQ0fHx0b29sfGVufDB8fHx8MTY2MTgxNjA4NA&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1530124566582-a618bc2615dc?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDQ0fHx0b29sfGVufDB8fHx8MTY2MTgxNjA4NA&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Choosing the right Amazon EC2 Spot Instances for your workloads"><p>On a recent project we were looking to take advantage of <a href="https://aws.amazon.com/ec2/spot/">Amazon EC2 spot instances</a>. The main issue was narrowing down the enormous list of instance types so they would be suitable to run our workloads. There are so many instance types with AWS, and they are growing all the time. To compound the problems, not all instance types are in every region.</p><p>Our key requirements were:</p><ul><li>8 vCPU</li><li>32 GB RAM</li><li>58 pods per node (we use the nodes in EKS)</li><li>Available in Sydney (<code>ap-southeast-2</code>)</li></ul><p>Rather than go through manually all the instances to ensure they meet the above minimum requirements (to which there are <a href="https://instances.vantage.sh/?min_memory=32&amp;min_vcpus=8&amp;region=ap-southeast-2">565 instance types</a> &#x1F632;) to find the most optimal instances, I thought there must be a better way (read: faster).</p><h2 id="introducing-amazon-ec2-instance-selector">Introducing Amazon EC2 Instance Selector</h2><p>This is a neat little CLI tool that seems like is a great fit for this exact problem.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/aws/amazon-ec2-instance-selector"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - aws/amazon-ec2-instance-selector: A CLI tool and go library which recommends instance types based on resource criteria like vcpus and memory</div><div class="kg-bookmark-description">A CLI tool and go library which recommends instance types based on resource criteria like vcpus and memory - GitHub - aws/amazon-ec2-instance-selector: A CLI tool and go library which recommends in...</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="Choosing the right Amazon EC2 Spot Instances for your workloads"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">aws</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/f5f9179f997362f495e510432d1c3eb31e0e229af094157fad42c9be362ec120/aws/amazon-ec2-instance-selector" alt="Choosing the right Amazon EC2 Spot Instances for your workloads"></div></a></figure><p>Installation is simple on a mac:</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">brew tap aws/tap
brew install ec2-instance-selector</code></pre><figcaption>Install Amazon EC2 Instance Selector on a Mac</figcaption></figure><p>Here is the command that I ended up using to select the instances. Of note, I am asking for:</p><ul><li>8 vCPU exactly and 32 GB RAM exactly. This seems to always <a href="https://github.com/awslabs/amazon-eks-ami/blob/master/files/eni-max-pods.txt">yield 58 pods/node density</a> when run in EKS. This is desirable for us.</li><li>Current generation instances only</li><li>Available in Sydney</li><li>Must be spot capable</li><li>No GPU instances, we don&apos;t have a use for these, and they cost loads</li><li>Deny the <code>t</code> series (not suitable for our workloads), deny the <code>i</code> series (not suitable for our workloads), deny the <code>r</code> series (not suitable for our workloads)</li><li>x86 architecture (perhaps ARM in the future)</li><li>No more than 30 cents an hour</li></ul><figure class="kg-card kg-code-card"><pre><code class="language-bash">ec2-instance-selector \
  --profile $PROFILE_NAME \
  --vcpus 8 \
  --memory-min 32 \
  --current-generation true \
  --region ap-southeast-2 \
  --usage-class spot \
  --gpus 0 \
  --max-results 50 \
  --deny-list &apos;^(t|i|r)&apos; \
  --cpu-architecture x86_64 \
  --price-per-hour-max 0.3 \
  --output table-wide</code></pre><figcaption>The full <code>ec2-instance-selector</code> command that we ended up using</figcaption></figure><p>This yields the following table:</p><figure class="kg-card kg-image-card kg-width-full kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2022/08/image.png" class="kg-image" alt="Choosing the right Amazon EC2 Spot Instances for your workloads" loading="lazy" width="2000" height="216" srcset="https://www.pixelite.co.nz/content/images/size/w600/2022/08/image.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2022/08/image.png 1000w, https://www.pixelite.co.nz/content/images/size/w1600/2022/08/image.png 1600w, https://www.pixelite.co.nz/content/images/size/w2400/2022/08/image.png 2400w"><figcaption><code>table-wide</code> output for the <code>ec2-instance-selector</code></figcaption></figure><p>Changing the output format to <code>one-line</code> for easy copy/paste.</p><pre><code class="language-bash">m4.2xlarge,m5.2xlarge,m5a.2xlarge,m5ad.2xlarge,m5d.2xlarge,m5zn.2xlarge,m6i.2xlarge</code></pre><p>And there you have it, this is how we were able to filter the giant list of AWS instance types to just 7. </p><p>I hope this ends up helping someone else in the future.</p>]]></content:encoded></item><item><title><![CDATA[Bulk import ACL entries into a Fastly ACL]]></title><description><![CDATA[How to automate adding large amounts of ACL entries to a Fastly ACL]]></description><link>https://www.pixelite.co.nz/article/bulk-import-acl-entries-into-a-fastly-acl/</link><guid isPermaLink="false">6202f66d67799a0007ff4f22</guid><category><![CDATA[Fastly]]></category><dc:creator><![CDATA[Sean Hamlin]]></dc:creator><pubDate>Tue, 08 Feb 2022 23:21:01 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1556757758-bcaf8510b51d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGVudHJ5fGVufDB8fHx8MTY0NDM2MjQyMw&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1556757758-bcaf8510b51d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGVudHJ5fGVufDB8fHx8MTY0NDM2MjQyMw&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Bulk import ACL entries into a Fastly ACL"><p>This came up recently, when a customer supplied a text file with several hundred CIDRs, and wanted these inputted into Fastly as an ACL (to control access to a certain site).</p><p>This was beyond my ability to do by hand, so here is a simple script to help automate this.</p><h2 id="step-1setup-fastly-cli">Step 1 - Setup Fastly CLI</h2><p>This tool is invaluable for dealing with Fastly on the command line. <a href="https://developer.fastly.com/reference/cli/#installing">Installation instructions can be found on Fastly&apos;s website</a>.</p><p>I then configure the CLI tool with environment variables in my <code>~/.zshrc</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">FASTLY_API_TOKEN=notarealtokenyo
export FASTLY_API_TOKEN
FASTLY_SERVICE_ID=banana
export FASTLY_SERVICE_ID</code></pre><figcaption>Environment variables to configure the Fastly CLI.</figcaption></figure><h2 id="step-2add-entries-to-the-acl">Step 2 - Add entries to the ACL</h2><p>You first need to actually create the ACL, you can use the Fastly UI for this, or the CLI. Once you have done that, you need to find the ID of the newly created ACL:</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">fastly acl list --version=latest</code></pre><figcaption>Find your ACL IDs with this Fastly CLI command.</figcaption></figure><p>To actually input the ACL entries, here is a small bash script to loop over each line in a text file, split by the <code>/</code> and then add the ACL entries with the subnet one at a time:</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">cat /tmp/ip.txt | while read LINE; do \
  IP=$(echo $LINE | cut -d &apos;/&apos; -f1)
  SUBNET=$(echo $LINE | cut -d &apos;/&apos; -f2)
  fastly acl-entry create --acl-id=$ACL_ID --ip=$IP --subnet=$SUBNET
done</code></pre><figcaption>Bulk import ACL entries with this simple script.</figcaption></figure><p>This yields something like:</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2022/02/acl.png" class="kg-image" alt="Bulk import ACL entries into a Fastly ACL" loading="lazy" width="1990" height="410" srcset="https://www.pixelite.co.nz/content/images/size/w600/2022/02/acl.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2022/02/acl.png 1000w, https://www.pixelite.co.nz/content/images/size/w1600/2022/02/acl.png 1600w, https://www.pixelite.co.nz/content/images/2022/02/acl.png 1990w" sizes="(min-width: 1200px) 1200px"><figcaption>Output of the script (with redactions).</figcaption></figure><p>Duplicate ACL entries are ignored, so you can run this script multiple times (if say you get an updated version with additional entries).</p><p>Hope you find this of some value.</p>]]></content:encoded></item><item><title><![CDATA[Dockerfile best practices - stories from the field]]></title><description><![CDATA[This is a collection of things I have come across using Docker over the years and working with docker in kubernetes across thousands of projects.]]></description><link>https://www.pixelite.co.nz/article/dockerfile-best-practices/</link><guid isPermaLink="false">61c14bb2aa1adc000765120f</guid><category><![CDATA[Docker]]></category><category><![CDATA[kubernetes]]></category><category><![CDATA[Performance]]></category><dc:creator><![CDATA[Sean Hamlin]]></dc:creator><pubDate>Tue, 04 Jan 2022 08:52:15 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1450045439515-ff27c2f2e6b1?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDZ8fHdoYWxlfGVufDB8fHx8MTY0MTI1Njg2NA&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1450045439515-ff27c2f2e6b1?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDZ8fHdoYWxlfGVufDB8fHx8MTY0MTI1Njg2NA&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Dockerfile best practices - stories from the field"><p>This is a collection of things I have come across using Docker over the years and working with kubernetes across thousands of projects. The hope is that there is a nugget of wisdom somewhere in this post, and that it can help on your next project.</p><p>I framed the post as a series of best practices, each one has a much larger story behind it, but I decided to keep this post positive &#x1F601;. I will likely continue to update this post as new information comes to light.</p><hr><h2 id="1-create-tiny-images">1. Create tiny images</h2><p>Your ideal image should be as small as possible to do its job. No unnecessary binaries, temporary files, development tools, uncompiled code, documentation etc should be in the final image.</p><blockquote class="kg-blockquote-alt"><em>Your ideal image should be as small as possible to do its job.</em><br>- me, just now</blockquote><p>Why this is important? Because <em>size matters </em>when it comes to docker images. The time to deploy your application into kubernetes will be largely determined by the image size. Smaller overall images push and pull faster into registries. Deployments are faster, applications scale horizontally faster in kubernetes. Life is generally better.</p><p>You can find the sizes of your local docker images with this handy command (<a href="https://stackoverflow.com/a/61091528/169882">source</a>):</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">$ docker images --format &quot;{{.ID}}\t{{.Size}}\t{{.Repository}}:{{.Tag}}&quot; | sort -k 2 -h -r
307b0123a4dd	936MB	cachingheaders:latest
c28b5b41a4dd	405MB	ghost-example_node:latest
dd54221ac341	393MB	ghost-pixelite_node:latest
441ccef7ac50	318MB	uselagoon/solr-7.7-drupal:latest
416cb5fc3d92	257MB	uselagoon/mariadb-10.5-drupal:latest
5e977a444840	244MB	uselagoon/mariadb:latest
d9300475ba78	236MB	uselagoon/mariadb-drupal:latest</code></pre><figcaption>Get the sizes of all docker images locally, in <code>MiB</code>.</figcaption></figure><p>In kubernetes, this is a little trickier, as the kubernetes <code>pod</code> API does not return image size, the <code>node</code> API does however, and with some <code>awk</code> magic, you can get a list of images sorted by size descending in MiB (<a href="https://stackoverflow.com/questions/62125346/list-container-images-in-kubernetes-cluster-with-size-like-docker-image-ls">inspired by this post</a>):</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">$ kubectl get nodes -o json \
  | jq -r &apos;.items[].status.images[] | &quot;\(.sizeBytes) \(.names[-1])&quot;&apos; \
  | sort -k 1 -n -r \
  | awk &apos;{ printf &quot;%d %s\n&quot;, $1/1024/1024, $2 }&apos;
771 harbor.example.com/example-2020/deploy-lagoon-development/cli@sha256:77947585cbf58bdae7e6a9182b104c2b2e30f535d9238cae0683dd571e3a3411
768 harbor.example.com/example2-2020/deploy-lagoon-staging/cli@sha256:6569474568b04f9bddb2194a66c4bf4a40bac79d936dc3bd864afd351f3f2200
768 harbor.example.com/example3-2020/deploy-lagoon-production/cli@sha256:95c3d048f50e33ea12d0b68b64034e0fc81d1b7b6d3f15b84e161a35e5417d72</code></pre><figcaption>Get the sizes of all images running in kubernetes, in <code>MiB</code>.</figcaption></figure><p>In general, I consider images in the low hundreds of MB to be OK, anything over 1GB to be large, and anything over 2 GB likely needs optimisation &#x1F631;. This obviously depends a lot on the language you are using (e.g. Go can build native executable binaries with no runtime), the size of the application (e.g. lines of code) and the Operating System required to run that code (e.g. Alpine Linux vs Ubuntu).</p><h3 id="avoid-adding-development-and-build-related-resources-to-the-final-image">Avoid adding development and build related resources to the final image</h3><p>The main issue with adding tools like <code>gcc</code> (which is needed to compile code) into your images is that you don&apos;t need this binary in production.</p><p>There are several techniques to deal with this ranging from virtual dependencies to builder images (covered later in this post).</p><figure class="kg-card kg-code-card"><pre><code class="language-docker"># Add build tools.
RUN apk update \
    &amp;&amp; apk add pv python3 make gcc g++ \
    &amp;&amp; ln -sf python3 /usr/bin/python \
    &amp;&amp; rm -rf /var/cache/apk/*</code></pre><figcaption>Adding build tools to your image is going to lead to (needlessly) larger images</figcaption></figure><p>Most package managers/languages also have flags you can use to toggle a production build. Production builds omit development dependencies, and will overall lead to smaller images.</p><figure class="kg-card kg-code-card"><pre><code class="language-docker"># PHP
RUN composer install --no-dev

# Nodejs
ENV NODE_ENV production
RUN npm ci --only=production
</code></pre><figcaption>PHP and Nodejs flags to ensure only needed dependencies in production.</figcaption></figure><p>Go builds can also be optimised a lot, and have things like cross platform bits removed, and debugging information. <a href="https://blog.filippo.io/shrink-your-go-binaries-with-this-one-weird-trick/">See this blog post</a> on some tips and tricks to reduce Go binary size.</p><h3 id="virtual-packages">Virtual packages</h3><p>In Alpine Linux you can also make use of virtual packages, to which allow you to install build related packages, and then clean them up. This is extremely useful for compilation.</p><figure class="kg-card kg-code-card"><pre><code class="language-bash"># Install libsvm, compile from source, and then remove dependencies.
COPY resources/libsvm.sh /tmp/libsvm.sh
RUN apk add --no-cache --virtual .build-deps \
    g++ \
    make \
    &amp;&amp; chmod +x /tmp/libsvm.sh \
    &amp;&amp; /tmp/libsvm.sh \
    &amp;&amp; rm /tmp/libsvm.sh \
    &amp;&amp; apk del .build-deps</code></pre><figcaption>Alpine Linux virtual packages come in handy to reduce image size.</figcaption></figure><p><strong>N.B. </strong>you should run all commands in the same <code>RUN</code> command to ensure the layer stays lightweight.</p><h3 id="builder-images-multi-stage-builds">Builder images (multi-stage builds)</h3><p>Probably the most popular way to keep your resulting images small where compilation is involved is to make use of a builder image (AKA multi-stage builds).</p><p>Say you have a Nodejs application, that requires a compilation step, but you don&apos;t want Nodejs installed in your production running container (just Nginx serving the static output).</p><p>This is sometimes called <em>multi-stage dockerfiles</em>, and has been a feature of docker since 2017.</p><p>For a real life example of this (based on actual code), first you need to create your &apos;builder&apos; intermediate image, this image will have the full development tools installed in it, and will be used to compile the application:</p><pre><code class="language-docker">FROM uselagoon/node-14-builder:latest as builder
RUN npm install --pure-lockfile
RUN npm install -y yarn
RUN yarn build</code></pre><p>After you have compiled the application (e.g. with <code>yarn</code> or something as above), you copy the resulting (static) artifacts into a clean final image:</p><pre><code class="language-docker">FROM uselagoon/nginx:latest
COPY --from=builder /app/dist /app</code></pre><p>The resulting Nginx image is fairly tiny, this particular one is around 133MB. This also has the nice side effect of being completely static, nothing to hack, no dynamic languages. A nice side effect of tiny images is a tiny attack surface. This will also make your security people happy.</p><p>For more information see <a href="https://codefresh.io/docker-tutorial/node_docker_multistage/">https://codefresh.io/docker-tutorial/node_docker_multistage/</a> and <a href="https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds">https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds</a></p><h3 id="select-the-best-base-image">Select the best base image</h3><p>A rookie docker mistake is to start from a fairly generic base image, and then customise it to the nth degree. There are vast libraries of images out there already that you can elect to start from to give yourself a head start.</p><p>Using another organisation&apos;s images does come with certain points to ponder:</p><ul><li>Who maintains the images?</li><li>How fast do they release new versions when a security issue is identified?</li><li>Do you trust them?</li><li>Is the code open source?</li></ul><p>At amazee.io, the Lagoon team looks after a <a href="https://hub.docker.com/u/uselagoon">suite of upstream docker images</a> in the uselagoon namespace. These images in turn (for the most part) inherit from specialised builds of Alpine Linux. e.g. this is a <a href="https://github.com/uselagoon/lagoon-images/blob/main/images/php-fpm/8.0.Dockerfile#L8">line from the PHP 8.0 FPM dockerfile</a>:</p><figure class="kg-card kg-code-card"><pre><code class="language-docker">FROM php:8.0.14-fpm-alpine3.14</code></pre><figcaption>Using a more specific base image from a reputable organisation will save you time and money.</figcaption></figure><p>By selecting the best starting point for your images, will mean less code you need to maintain, less layers in your dockerfiles, and faster builds.</p><p>Most applications also function perfectly well on <a href="https://www.alpinelinux.org/about/">Alpine Linux</a> (a lightweight Linux distribution) to which can run on a 8MB image. If you have not checked this out, do it, your dockerhost thanks you in advance.</p><p>If you are using Rust or Go, you should consider using <code>scratch</code> (<a href="https://chemidy.medium.com/create-the-smallest-and-secured-golang-docker-image-based-on-scratch-4752223b7324">blog post on this topic</a>) or <code>distroless static</code> (<a href="https://blog.baeke.info/2021/03/28/distroless-or-scratch-for-go-apps/">blog post on this topic</a>) as your base image (which are extremely basic and lightweight images). <code>Distroless static</code> is the same as <code>scratch</code> but <a href="https://github.com/GoogleContainerTools/distroless/blob/main/base/README.md">with a few niceties</a> such as CA certificates installed, a functioning <code>/tmp</code> directory etc. All of this for 1 additional MB, sounds pretty good.</p><hr><h2 id="2-docker-layer-caching-dlc">2. Docker Layer Caching (DLC)</h2><p>During a build, docker steps through each line one at a time. As each line is read, docker attempts to match this against it&apos;s cache to see if the step can be re-used from cache. </p><p>Making effective use of layer caching can speed up your build times a lot. It is important to note that only certain commands create layers, namely <code>ADD</code>, <code>COPY</code> and <code>RUN</code>.</p><p>See <a href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#leverage-build-cache">https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#leverage-build-cache</a> </p><h3 id="ordering-layers-to-increase-cache-hits">Ordering layers to increase cache hits</h3><p>The order in which you create layers matters. In general, you want to have the most static things at the beginning of the docker file (e.g. environment variables) and more volatile things towards the end (e.g. code changes by a developer). </p><p>As soon as you have a cache miss on a layer, then all subsequent layers will need to be rebuilt. </p><p>This is especially useful for <code>COPY</code> commands. You want to structure the dockerfile such that the files that are most frequently changes are copied in as late as possible.</p><h3 id="inlining-commands-to-reduce-layers">Inlining commands to reduce layers</h3><p>Rather that create a layer for each similar command, you can chain lines with a backslash <code>\</code> to ensure only a single layer is created.</p><pre><code>RUN apk --no-cache add \
    bash \
    bind-tools \
    ca-certificates \
    curl \
    git \
    unzip</code></pre><p>A pro tip is to ensure only 1 package per line, and keep them in alphabetical order. This helps to ensure the next PR to update the list is a tiny bit easier.</p><p>See <a href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#minimize-the-number-of-layers">https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#minimize-the-number-of-layers</a> </p><h3 id="breaking-layer-cache">Breaking layer cache</h3><p>You will run into situations where Operating System packages, NPM packages or a Git repo are updated to newer versions but as your Dockerfile or <code>package.json</code> hasn&apos;t updated, docker will continue using the cache. This may be less than ideal for your particular circumstance.</p><p>A quick way to &apos;bust the cache&apos; is to define the use of a build time variable, and run a simple command to use it. <a href="https://docs.lagoon.sh/using-lagoon-the-basics/build-and-deploy-process/#3-build-image">Lagoon injects the SHA of the commit</a> as such a variable, so if you do want to ensure the layer is built fresh, you can quite easily:</p><figure class="kg-card kg-code-card"><pre><code class="language-docker">ARG LAGOON_GIT_SHA
RUN echo $LAGOON_GIT_SHA</code></pre><figcaption>Force Lagoon to break layer cache from this command forward.</figcaption></figure><p>Other build systems will likely have a similar way to obtain the SHA of the commit.</p><hr><h2 id="3-dockerignore-files">3. <code>.dockerignore</code> files</h2><p>This will prevent certain local files and debug logs from being copied onto your Docker image and possibly overwriting files installed within your image during the build.</p><p>It also is a good idea to not copy your dockerfiles themselves into the docker image.</p><figure class="kg-card kg-code-card"><pre><code class="language-bash"># PHP
.git
.idea
.ahoy.yml
.circleci
.dockerignore
.gitignore
Dockerfile
docker-compose.yml
README.md
vendor
web/sites/default/files

# Nodejs
.dockerignore
node_modules
npm-debug.log
Dockerfile
.git
.gitignore
.npmrc</code></pre><figcaption>Example <code>.dockerignore</code> for PHP and Nodejs.</figcaption></figure><p>My best advice here is to SSH into a running container and inspect the files you have left in there. If you see anything that is not essential to production runtime, then consider not <code>COPY</code>&apos;ing it, or adding it to <code>.dockerignore</code>.</p><p>See <a href="https://docs.docker.com/engine/reference/builder/#dockerignore-file">https://docs.docker.com/engine/reference/builder/#dockerignore-file</a></p><hr><h2 id="comments">Comments</h2><p>If you have any neat tips or tricks, please let me know!</p>]]></content:encoded></item><item><title><![CDATA[Fastly Soft Purge vs Instant Purge - what is the difference?]]></title><description><![CDATA[What Fastly Soft Purge is, when it is useful, and how to use it.]]></description><link>https://www.pixelite.co.nz/article/fastly-soft-purge-vs-instant-purge/</link><guid isPermaLink="false">61c23fb3aa1adc0007651217</guid><category><![CDATA[Fastly]]></category><category><![CDATA[Drupal]]></category><category><![CDATA[Drupal planet]]></category><category><![CDATA[Caching]]></category><category><![CDATA[Varnish]]></category><dc:creator><![CDATA[Sean Hamlin]]></dc:creator><pubDate>Wed, 29 Dec 2021 04:36:59 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1503164046765-c2dd32d51708?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDExfHxmZWF0aGVyfGVufDB8fHx8MTY0MDc1MjQyNQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1503164046765-c2dd32d51708?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDExfHxmZWF0aGVyfGVufDB8fHx8MTY0MDc1MjQyNQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Fastly Soft Purge vs Instant Purge - what is the difference?"><p>This topic came up a while ago when Acquia was looking to <a href="https://www.drupal.org/project/acquia_purge/issues/3254161">introduce Soft Purge as an option for their Acquia purge module</a> in Drupal (N.B. not to be confused by the <a href="https://www.drupal.org/project/fastly">Fastly Drupal module</a>).</p><p>There appears to be some confusion about what Fastly Soft Purge is, when it is useful, and how to use it. This post will help clear this topic up.</p><h2 id="what-is-fastly-soft-purge">What is Fastly Soft Purge?</h2><p>In Varnish, to force a miss for an object in cache you have a couple of options</p><ul><li>evict it completely (e.g. <code>PURGE</code>, or <code>BAN</code> with a lurker). The <a href="https://varnish-cache.org/docs/trunk/users-guide/purging.html">Varnish documentation cover this</a>. This is the same as Fastly Instant Purge.</li><li>or simply mark the object as expired. I imagine Fastly achieve this by tinkering with the TTL of the cached object. This is what Fastly Soft Purge is.</li></ul><p>So the main difference with Fastly Soft Purge is that the object persists in cache, and can be used in a stale context. This can prove to be extremely useful.</p><h2 id="when-is-fastly-soft-purge-useful">When is Fastly Soft Purge useful?</h2><p>I would say <strong>Fastly Soft Purge should be used in 99.99% of all purges</strong>. There are extremely limited situations in which I would recommend Fastly Instant Purge:</p><ul><li>You accidentally published embargoed content, or there is some legal issue with the content</li><li>You accidentally published incorrect or incomplete content</li><li>You accidentally published test content (we have all been there)</li><li>You need to destroy the entire cache for a given Fastly service &#x1F631; (which I never really recommend)</li></ul><p>It boils down to making a whoopsie really, and I would imagine the vast majority of your purges should (hopefully) not fall into these categories.</p><p>Having a stale object is cache is useful as it:</p><ul><li>soft purges to high traffic pages (e.g. the homepage) will not result in slow downs for new users, this is because &quot;Stale while revalidate&quot; will perform a background (asynchronous) refresh of the cached object, and will serve the cached stale object in the meantime.</li><li>if your origin is completely down (or in maintenance) stale objects can be served to users (which is often better than a generic error page). This is the &quot;Stale if error&quot; feature.</li></ul><h2 id="how-to-use-fastly-soft-purge-with-the-drupal-module">How to use Fastly Soft Purge with the Drupal module</h2><p>Just because you execute a purge to Fastly does not mean you will instantly get any benefit, you also need to combo this with some &quot;Stale Content Options&quot; to which I will go through.</p><p>If you are using the Fastly Drupal module, Soft Purge is configured like so:</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2021/12/purge.png" class="kg-image" alt="Fastly Soft Purge vs Instant Purge - what is the difference?" loading="lazy" width="1882" height="1012" srcset="https://www.pixelite.co.nz/content/images/size/w600/2021/12/purge.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2021/12/purge.png 1000w, https://www.pixelite.co.nz/content/images/size/w1600/2021/12/purge.png 1600w, https://www.pixelite.co.nz/content/images/2021/12/purge.png 1882w" sizes="(min-width: 1200px) 1200px"><figcaption>The &quot;Purge options&quot; administration page in the Fastly Drupal module where you configure soft purging.</figcaption></figure><p>And as for Stale Content Options, you need to ensure that both &quot;Stale while revalidate&quot; and &quot;Stale if error&quot; are enabled. These are some basic tuning for the values as well:</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2021/12/stale-1.png" class="kg-image" alt="Fastly Soft Purge vs Instant Purge - what is the difference?" loading="lazy" width="1818" height="970" srcset="https://www.pixelite.co.nz/content/images/size/w600/2021/12/stale-1.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2021/12/stale-1.png 1000w, https://www.pixelite.co.nz/content/images/size/w1600/2021/12/stale-1.png 1600w, https://www.pixelite.co.nz/content/images/2021/12/stale-1.png 1818w" sizes="(min-width: 1200px) 1200px"><figcaption>The &quot;Stale content options&quot; administration page in the Fastly Drupal module.</figcaption></figure><p>If you are not using Drupal (or using the Acquia purge module), then you can always set the above HTTP headers manually. See the <a href="https://docs.fastly.com/en/guides/serving-stale-content#manually-enabling-serve-stale">Fastly documentation on this</a>.</p><h2 id="how-to-use-fastly-soft-purge-with-the-api-only">How to use Fastly Soft Purge with the API only</h2><p>If you don&apos;t run Drupal, or just want to execute a soft purge via the Fastly API, then this is simple as well, just pass in the <code>Fastly-Soft-Purge</code> HTTP header:</p><pre><code class="language-bash">curl -sXPOST https://api.fastly.com/service/SERVICE_ID/purge/TAG \
 -H &apos;Fastly-Soft-Purge:1&apos; \
 -H &quot;Fastly-Key:$FASTLY_TOKEN&quot; | jq
{
  &quot;status&quot;: &quot;ok&quot;,
  &quot;id&quot;: &quot;10427-1615460322-6&quot;
}</code></pre><h2 id="wait-i-want-to-learn-more">Wait, I want to learn more</h2><p>If you want to go deeper in Varnish (what powers Fastly), and how objects, TTL, stale and grace (stale-while-revalidate) are involved, please <a href="https://info.varnish-software.com/blog/how-to-set-and-override-ttl">see their documentation</a>.</p><p>There is also this excellent graphic to which explains how an object goes from fresh to stale</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2021/12/ttl.png" class="kg-image" alt="Fastly Soft Purge vs Instant Purge - what is the difference?" loading="lazy" width="1600" height="540" srcset="https://www.pixelite.co.nz/content/images/size/w600/2021/12/ttl.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2021/12/ttl.png 1000w, https://www.pixelite.co.nz/content/images/2021/12/ttl.png 1600w" sizes="(min-width: 1200px) 1200px"><figcaption>Cached object timeline and how they interact</figcaption></figure><h2 id="gotchas">Gotchas</h2><p>If you use Fastly shielding (which you probably should), then there is the potential for soft purged content on the edge PoP to end up being re-cached as fresh &#x1F631;. <a href="https://developer.fastly.com/learning/concepts/purging/#stale-content-becoming-fresh">See the Fastly documentation</a> on this. There is a <a href="https://developer.fastly.com/learning/concepts/stale/#shielding-considerations">VCL snippet to mitigate this</a>:</p><figure class="kg-card kg-code-card"><pre><code class="language-vcl">if (fastly.ff.visits_this_service &gt; 0) {
  set req.max_stale_while_revalidate = 0s;
}</code></pre><figcaption><code>vcl_recv</code> snippet to mitigate soft purged content being re-cached as fresh when shielding is enabled in Fastly.</figcaption></figure><p>If you are not already using <a href="https://developer.fastly.com/learning/concepts/shielding/">Fastly shielding</a>, and you care about caching, and cache hit rates, you should really start to look at this. The benefits far outweigh the tiny edge cases.</p><p><strong>P.S.</strong> if you are not already using the <a href="https://docs.fastly.com/en/guides/authenticating-api-purge-requests">Fastly purge auth snippet</a>, get this installed ASAP. By not doing this, a bad actor is able to purge any single URL on your site, in an instant fashion. This could create a DoS.</p><figure class="kg-card kg-code-card"><pre><code class="language-vcl"># Fastly&apos;s URL purge feature allows you to purge individual URLs on your
# website. By default, authentication is not required to purge a URL with the
# Fastly API, but you can enable API token authentication with this snippet.
#
# @see https://docs.fastly.com/en/guides/authenticating-api-purge-requests
if ( req.request == &quot;FASTLYPURGE&quot; ) {
    set req.http.Fastly-Purge-Requires-Auth = &quot;1&quot;;
}</code></pre><figcaption><code>vcl_recv</code> snippet to enable single path URL purge with authentication in Fastly.</figcaption></figure><h2 id="comments">Comments</h2><p>If you have any other pro tips when it comes to soft purging in Fastly, please let me know.</p>]]></content:encoded></item><item><title><![CDATA[Fastly Logging to Amazon S3 in JSON format]]></title><description><![CDATA[One of the cheapest and easiest methods for logging in Fastly is to use Amazon S3. This post will examine how to set this logging up, some of the common gotchas I came across after running this in production for a while now.]]></description><link>https://www.pixelite.co.nz/article/fastly-logging-to-amazon-s3-in-json-format/</link><guid isPermaLink="false">60def49afc441200063df1e1</guid><category><![CDATA[Fastly]]></category><category><![CDATA[Log]]></category><category><![CDATA[Analytics]]></category><category><![CDATA[Bash]]></category><dc:creator><![CDATA[Sean Hamlin]]></dc:creator><pubDate>Sat, 25 Sep 2021 05:04:14 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1456255985051-dcbc4f615823?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDEzfHxsb2dzfGVufDB8fHx8MTYzMjUzNDU0OQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1456255985051-dcbc4f615823?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDEzfHxsb2dzfGVufDB8fHx8MTYzMjUzNDU0OQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Fastly Logging to Amazon S3 in JSON format"><p>One of the cheapest and easiest methods for logging in Fastly is to use <a href="https://aws.amazon.com/s3/">Amazon S3</a>. S3 is extremely low cost, and suitable for long term storage and archival of logs.</p><p>This post will examine how to set this logging up, some of the common gotchas I came across after running this in production for a while now. The post will also explore how to download and analyse the logs.</p><h2 id="create-a-new-logging-endpoint">Create a new logging endpoint</h2><p>In the desired service, visit the <code>Logging</code> section, and add a new <code>Amazon S3</code> endpoint.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2021/09/image-2.png" class="kg-image" alt="Fastly Logging to Amazon S3 in JSON format" loading="lazy" width="2000" height="359" srcset="https://www.pixelite.co.nz/content/images/size/w600/2021/09/image-2.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2021/09/image-2.png 1000w, https://www.pixelite.co.nz/content/images/size/w1600/2021/09/image-2.png 1600w, https://www.pixelite.co.nz/content/images/size/w2400/2021/09/image-2.png 2400w" sizes="(min-width: 1200px) 1200px"><figcaption>Fastly has a number of options for logging, including to Amazon S3.</figcaption></figure><h2 id="creating-a-custom-json-log-format">Creating a custom JSON log format</h2><p>Fastly comes with a default, I choose to log specific fields that are useful for debugging and analysis later.</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">{ &quot;timestamp&quot;:%{time.start.sec}V, &quot;service_id&quot;:&quot;%{req.service_id}V&quot;, &quot;time_elapsed&quot;:%{time.elapsed.usec}V, &quot;is_edge&quot;:%{if(fastly.ff.visits_this_service == 0, &quot;true&quot;, &quot;false&quot;)}V, &quot;client_ip&quot;:&quot;%{req.http.Fastly-Client-IP}V&quot;, &quot;client_as&quot;:&quot;%{if(client.as.number!=0, client.as.number, &quot;null&quot;)}V&quot;, &quot;geo_city&quot;:&quot;%{client.geo.city}V&quot;, &quot;geo_country_code&quot;:&quot;%{client.geo.country_code}V&quot;, &quot;request&quot;:&quot;%{req.request}V&quot;, &quot;host&quot;:&quot;%{json.escape(req.http.Host)}V&quot;, &quot;url&quot;:&quot;%{json.escape(req.url)}V&quot;, &quot;request_referer&quot;:&quot;%{json.escape(req.http.Referer)}V&quot;, &quot;request_user_agent&quot;:&quot;%{json.escape(req.http.User-Agent)}V&quot;, &quot;resp_status&quot;:&quot;%s&quot;, &quot;resp_message&quot;:&quot;%{resp.response}V&quot;, &quot;resp_body_size&quot;:%{resp.body_bytes_written}V, &quot;server_datacenter&quot;:&quot;%{server.datacenter}V&quot;, &quot;cache_status&quot;:&quot;%{fastly_info.state}V&quot;, &quot;waf_block&quot;:&quot;%{req.http.X-Waf-Block}V&quot;, &quot;waf_block_id&quot;:&quot;%{req.http.X-Waf-Block-Id}V&quot; }

</code></pre><figcaption>Log format that will write valid JSON</figcaption></figure><p>An example request will look like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">{
  &quot;timestamp&quot;: 1629796513,
  &quot;service_id&quot;: &quot;4gJgt7cCs3QChTi2evKdke&quot;,
  &quot;time_elapsed&quot;: 1064614,
  &quot;is_edge&quot;: true,
  &quot;client_ip&quot;: &quot;107.170.227.23&quot;,
  &quot;client_as&quot;: &quot;14061&quot;,
  &quot;geo_city&quot;: &quot;san francisco&quot;,
  &quot;geo_country_code&quot;: &quot;US&quot;,
  &quot;request&quot;: &quot;GET&quot;,
  &quot;host&quot;: &quot;www.example.com&quot;,
  &quot;url&quot;: &quot;/&quot;,
  &quot;request_referer&quot;: &quot;http://www.example.com/&quot;,
  &quot;request_user_agent&quot;: &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36&quot;,
  &quot;resp_status&quot;: &quot;200&quot;,
  &quot;resp_message&quot;: &quot;OK&quot;,
  &quot;resp_body_size&quot;: 16362,
  &quot;server_datacenter&quot;: &quot;PAO&quot;,
  &quot;cache_status&quot;: &quot;MISS-CLUSTER&quot;,
  &quot;waf_block&quot;: &quot;0&quot;,
  &quot;waf_block_id&quot;: &quot;0&quot;
}</code></pre><figcaption>Example request in JSON format</figcaption></figure><h3 id="extending-the-format">Extending the format</h3><p>You can log pretty much anything you want here, there is a <a href="https://docs.fastly.com/en/guides/useful-variables-to-log">comprehensive list in the Fastly docs</a> about other variables you can log.</p><h2 id="gotchas">Gotchas</h2><p>During the development of our logging format several things have cropped up that I have had to solve:</p><h3 id="duplicate-logs-with-shielding">Duplicate logs with shielding</h3><p>If you enable <a href="https://docs.fastly.com/en/guides/shielding">shielding</a>, then every request from an end user could potentially be 2 logs (edge PoP and shield PoP). In order to counter this, you can filter logs by <code>is_edge</code> being <code>true</code>. The below <code>jq</code> examples will show how to do this.</p><h3 id="escape-user-supplied-fields">Escape user supplied fields</h3><p>If you find this out the hard way when your JSON is no longer valid. An easy rule of thumb is - if it is supplied by the end user, then you must escape it</p><figure class="kg-card kg-code-card"><pre><code class="language-c">&quot;host&quot;:&quot;%{json.escape(req.http.Host)}V&quot;</code></pre><figcaption>the <code>Host</code> header is supplied by the user, and thus must be escaped</figcaption></figure><h3 id="truncate-user-supplied-fields">Truncate user supplied fields</h3><p>I also saw examples where the user agent supplied was thousands of characters long, the same with the referrer. Fastly has some hidden truncation logic of it&apos;s own (around 1,500 characters or something), so the full log line will not be written. The solution was to deploy this VCL snippet in <code>vcl_recv</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-c">if ( req.http.request_referer ~ &quot;.{1000,}&quot; ) {
  set req.http.request_referer = regsub(req.http.request_referer, &quot;(.{0,125}).*&quot;, &quot;\1&quot;);
}
if ( req.http.User-Agent ~ &quot;.{1000,}&quot; ) {
  set req.http.User-Agent = regsub(req.http.User-Agent, &quot;(.{0,125}).*&quot;, &quot;\1&quot;);
}</code></pre><figcaption>Truncate user supplied strings if they are excessively long, as these can mess up logging to JSON.</figcaption></figure><h3 id="set-the-s3-domain-correctly">Set the S3 domain correctly</h3><p>Unless your S3 bucket is in <code>us-east-1</code> it is likely you will need to customise the domain for the S3 bucket. Ensure this is correct, else you will not see a single</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2021/09/image-1.png" class="kg-image" alt="Fastly Logging to Amazon S3 in JSON format" loading="lazy" width="1324" height="254" srcset="https://www.pixelite.co.nz/content/images/size/w600/2021/09/image-1.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2021/09/image-1.png 1000w, https://www.pixelite.co.nz/content/images/2021/09/image-1.png 1324w" sizes="(min-width: 720px) 720px"><figcaption><code>s3.ap-southeast-2.amazonaws.com</code> is the domain for S3 buckets in Sydney</figcaption></figure><h3 id="log-line-format-should-be-blank">Log line format should be blank</h3><p>This is now the default (it was not a while back), just ensure this stays on <code>Blank</code>, else Fastly will write &apos;helpful&apos; timestamps to each line, and mess up the JSON syntax.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2021/09/image.png" class="kg-image" alt="Fastly Logging to Amazon S3 in JSON format" loading="lazy" width="1532" height="384" srcset="https://www.pixelite.co.nz/content/images/size/w600/2021/09/image.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2021/09/image.png 1000w, https://www.pixelite.co.nz/content/images/2021/09/image.png 1532w" sizes="(min-width: 720px) 720px"><figcaption>Set the log line format to <code>blank</code> to ensure JSON logs stay JSON</figcaption></figure><h2 id="downloading-the-logs">Downloading the logs</h2><p>Now that logs are flowing into your S3 bucket, if you actually want to interact with them, it is likely better to download them. This is simple with the <a href="https://aws.amazon.com/cli/">AWS CLI</a>:</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">aws --profile PROFILE_NAME s3 sync s3://BUCKET_NAME/ . --exclude &apos;*&apos; --include &apos;2021-09-25*&apos;
</code></pre><figcaption>download 1 day&apos;s worth of logs using the AWS CLI</figcaption></figure><p>This will produce a lot of log files (1 per PoP per hour). In order to do analysis on them, it is easier to have a giant log file:</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">find . -maxdepth 1 -name &quot;*.log&quot; -print0 | xargs -0 cat &gt; logs.json</code></pre><figcaption>concatenate all the individual logs to a super log</figcaption></figure><h2 id="analyse-your-fastly-logs"><strong>Analyse your Fastly logs</strong></h2><p>Now that you have the raw data, you should look to turn it into something you can make business decisions with.</p><p>Here are some simple analysis you can do with the <code>jq</code> tool (ensure <a href="https://github.com/stedolan/jq/wiki/Installation">you install this first</a> if you have not already).</p><h3 id="top-uris">Top URIs</h3><pre><code class="language-bash">jq -r &apos;select(.is_edge) | .url&apos; ${FILENAME} | sort -n | uniq -c | sort -nr | head -n 3

704990 /
670605 /sitewide_alert/load
109435 /ajax/site_alert</code></pre><h3 id="top-user-agents">Top user agents</h3><pre><code class="language-bash">jq -r &apos;select(.is_edge) | .request_user_agent&apos; ${FILENAME} | sort -n | uniq -c | sort -nr | head -n 3


580342 Elastic-Heartbeat/7.14.0
418879 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36
290797 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36</code></pre><h3 id="top-http-404s">Top HTTP 404s</h3><pre><code class="language-bash">jq &apos;select(.is_edge and (.resp_status == &quot;404&quot;)) | &quot;\(.host)\(.url)&quot;&apos; ${FILENAME} | sort -n | uniq -c | sort -nr | head -n 3

58791 &quot;www.example.com/sites/default/files/logo.png&quot;
58117 &quot;www.example.com/sites/default/files/social/Facebook.png&quot;
58021 &quot;www.example.com/sites/default/files/social/LinkedIn.png&quot;</code></pre><h2 id="comments">Comments</h2><p>If you have any neat tips and tricks around logging with Fastly to S3, please let me know in the comments. </p>]]></content:encoded></item><item><title><![CDATA[Hosting Ghost in Kubernetes]]></title><description><![CDATA[Ghost is traditional meant to be installed on a single VM. This article will explore how to get Ghost running inside Kubernetes.]]></description><link>https://www.pixelite.co.nz/article/hosting-ghost-in-kubernetes/</link><guid isPermaLink="false">60eceb7b8f7795000af8c3a2</guid><category><![CDATA[Ghost]]></category><category><![CDATA[Fastly]]></category><category><![CDATA[kubernetes]]></category><dc:creator><![CDATA[Sean Hamlin]]></dc:creator><pubDate>Tue, 13 Jul 2021 02:59:54 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1514944152559-a103040c7f16?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDh8fGdob3N0fGVufDB8fHx8MTYyNjE0NTE3OQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1514944152559-a103040c7f16?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDh8fGdob3N0fGVufDB8fHx8MTYyNjE0NTE3OQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Hosting Ghost in Kubernetes"><p>Ghost currently is geared towards only hosting 1 copy of Ghost per site, in fact this is <a href="https://ghost.org/docs/faq/clustering-sharding-multi-server/">explicitly written in the FAQs</a>:</p><blockquote>Ghost doesn&#x2019;t support load-balanced clustering or multi-server setups of any description, there should only be one Ghost instance per site.</blockquote><p>and also the <a href="https://ghost.org/docs/hosting/">hosting page</a>:</p><blockquote>Clustering or sharding is not supported in any way.</blockquote><p>At my work <a href="https://www.amazee.io/">amazee.io</a> we specialise in hosting applications in Kubernetes (both native Kubernetes and Openshift), so I thought of this like a challenge to see how well Ghost would sit on a completely different architecture then a single VM in DigitalOcean (<a href="https://ghost.org/docs/hosting/">Ghost recommends this as a web host</a>).</p><h2 id="why-is-kubernetes-so-different-from-a-traditional-vm">Why is Kubernetes so different from a traditional VM</h2><p>Kubernetes runs your applications inside containers, and those containers run in pods. Your pod at any one time may be running, or it may not (e.g. it may be in the process of being moved to another node). Building resilient architectures in Kubernetes often involves ensuring your application can run in a Highly Available (HA) setup, which often means running 2 or more pods at any one time. It also means avoiding any single point of failures.</p><p>This way your application can survive the loss of a single node, or even Availability Zone with zero changes (it is transparent to your application).</p><p>This is a radical deviation from Ghost&apos;s recommended hosting approach.</p><h2 id="approach-to-getting-ghost-running-on-kubernetes">Approach to getting Ghost running on Kubernetes</h2><p>The approach I took was:</p><ul><li>Inherit from an existing <a href="https://hub.docker.com/_/ghost">official docker image for Ghost</a> (to avoid me having to learn how to install Ghost). <a href="https://github.com/docker-library/ghost">Github repo is here</a>. I used this image as a builder image.</li><li>Use a Node.js 14 based image with Alpine Linux as the Operating System (to ensure the image is as small as possible). Node.js 14 is also an LTS version (<a href="https://endoflife.date/nodejs">with security support to April 2023</a>), it is also <a href="https://ghost.org/docs/faq/node-versions/">Ghost&apos;s recommended version</a> to run.</li><li>Use a Persistent Volume (PV) for all uploaded files (e.g. images, themes etc) in Ghost (path is <code>/var/lib/ghost/content/</code>). This way these files will survive pod restarts. The PV will be <code>ReadWriteMany</code> to ensure multiple pods can read and write to the PV at the same time.</li><li>Use a MySQL database through the Cloud provider in question (in my case AWS RDS Aurora). You never want to host a database yourself in Kubernetes, if you want it to be HA. You certainly do not want to use SQLite.</li><li>Shim the supplied environment variables into the names that Ghost expects.</li></ul><p>The above approach ended up working quite well, as you will see later on, the code to get this done is quite tiny.</p><h2 id="what-this-ends-up-looking-like">What this ends up looking like</h2><p>Here is a screenshot of LensIDE to which lists the pods running for <em>this</em> site (meta am I right):</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2021/07/image.png" class="kg-image" alt="Hosting Ghost in Kubernetes" loading="lazy" width="2000" height="251" srcset="https://www.pixelite.co.nz/content/images/size/w600/2021/07/image.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2021/07/image.png 1000w, https://www.pixelite.co.nz/content/images/size/w1600/2021/07/image.png 1600w, https://www.pixelite.co.nz/content/images/size/w2400/2021/07/image.png 2400w" sizes="(min-width: 1200px) 1200px"><figcaption>A screenshot of this site running in Kubernetes, with an HPA of 2 pods minimum</figcaption></figure><p>I also created an Horizontal Pod Autoscaler (HPA) to show that you can scale this deployment with load automatically</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2021/07/image-1.png" class="kg-image" alt="Hosting Ghost in Kubernetes" loading="lazy" width="2000" height="222" srcset="https://www.pixelite.co.nz/content/images/size/w600/2021/07/image-1.png 600w, https://www.pixelite.co.nz/content/images/size/w1000/2021/07/image-1.png 1000w, https://www.pixelite.co.nz/content/images/size/w1600/2021/07/image-1.png 1600w, https://www.pixelite.co.nz/content/images/2021/07/image-1.png 2322w" sizes="(min-width: 1200px) 1200px"><figcaption>A screenshot of this site&apos;s HPA running in Kubernetes</figcaption></figure><p>The HPA likely will not be used (Ghost is not heavy on the CPU), but it is there in any case.</p><p><strong>N.B. </strong>there appears to be some <a href="https://forum.ghost.org/t/external-in-memory-cache-option/1140/2">in-memory caching that Ghost does</a>, this means that running 2 pods minimum will cause issues (e.g. some URLs will respond with HTTP 404 on newly created posts for example). Likely best to run with 1 pod for now. </p><h2 id="example-repository">Example repository</h2><p>I created an example to show this in action, that you can use to give you a headstart:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/seanhamlin/ghost-example"><div class="kg-bookmark-content"><div class="kg-bookmark-title">seanhamlin/ghost-example</div><div class="kg-bookmark-description">Contribute to seanhamlin/ghost-example development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Hosting Ghost in Kubernetes"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">seanhamlin</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/1f86505298f9f7a304c34c3f99cbc4f7a45cedfdb6c7702fdaff87b1ee293f59/seanhamlin/ghost-example" alt="Hosting Ghost in Kubernetes"></div></a></figure><h2 id="things-still-to-dogotchas">Things still to do/gotchas</h2><p>A few things I still need to solve, and/or write about:</p><ul><li>Email configuration - not essential for this blog, but it would be nice to actually get this working. Seems like just a few more environment variables to configure.</li><li>The PV mount replaces the default files at <code>/var/lib/ghost/content/</code>, so the theme Casper will not be available the first time the application runs. Ghost gets grumpy about that. This likely can be fixed with another entrypoint to copy the files into the correct place during pod startup, if the files are missing.</li><li>How I setup caching in Fastly. Ghost does not play nicely with CDNs out of the box. I had to tune this.</li><li>Fix Systemd complaints - does not seem to be in Alpine Linux</li></ul><p>I will update this blog post as further details come in and I solve the above things.</p><h2 id="comments">Comments</h2><p>Keen to hear from anyone else that in interested in this, or has questions or concerns about this approach.</p>]]></content:encoded></item><item><title><![CDATA[Purge a single domain and it's content from a Fastly service with multiple domains]]></title><description><![CDATA[One of the current limitations of Fastly is that you cannot purge a single domain from a service, without dropping the entire cache. This post will explore a solution to this.]]></description><link>https://www.pixelite.co.nz/article/purge-a-single-domain-and-its-content-from-a-fastly-service/</link><guid isPermaLink="false">60def49afc441200063df1eb</guid><category><![CDATA[Fastly]]></category><category><![CDATA[Varnish]]></category><category><![CDATA[Caching]]></category><dc:creator><![CDATA[Sean Hamlin]]></dc:creator><pubDate>Sat, 27 Mar 2021 02:45:32 GMT</pubDate><media:content url="https://www.pixelite.co.nz/content/images/2021/03/lee-scott-_VTgctRg0tA-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://www.pixelite.co.nz/content/images/2021/03/lee-scott-_VTgctRg0tA-unsplash.jpg" alt="Purge a single domain and it&apos;s content from a Fastly service with multiple domains"><p>How we configure <a href="https://www.fastly.com/">Fastly</a> at work is typically to use a single Fastly service and add multiple domains to each service. This works well for a number of reasons:</p><ul><li><strong>Configuration</strong> - each service has it&apos;s own configuration and VCL snippets. It is common to have the same configuration extent to multiple sites.</li><li><strong>Cost</strong> - number of services is a factor in Fastly costs</li></ul><p>One of the current limitations out of the box, is that you cannot purge a single domain (and its contents) from a service, without dropping the entire cache. This post will explore a solution to this.</p><h2 id="altering-surrogate-key-in-fastly-vcl">Altering <code>Surrogate-Key</code> in Fastly VCL</h2><p>It turns out you can add a custom snippet in <code>vcl_fetch</code> to which will adjust the response from origin, to create (or append to) the current domain to HTTP header <code>Surrogate-Key</code>.</p><figure class="kg-card kg-code-card"><pre><code class="language-c"># We want to append the host of the request to the response Surrogate-Key
# header, or create it, if it does not already exist. We only want to do this
# once, and this includes if shielding is in use.
#
# This is so we can purge the domain, of all requests, including static files,
# without touching other domains on the service.
if ( !beresp.http.Surrogate-Key ) {
  set beresp.http.Surrogate-Key = req.http.host;
}
else {
  if ( !std.strstr(beresp.http.Surrogate-Key, req.http.host) ){
    set beresp.http.Surrogate-Key = beresp.http.Surrogate-Key &quot; &quot; req.http.host;
  }
}</code></pre><figcaption>VCL code that resides in the <code>fetch</code> method for adding the current domain to the list of cache tags found in the header <code>Surrogate-Key</code>&#xA0;</figcaption></figure><p>N.B. This new logic will only apply to new objects in cache only, existing cached objects will be unaffected. It pays to think ahead for this logic. You might also consider dropping the entire service cache after you have the VCL in place (if this is an option).</p><h2 id="how-to-test-if-it-is-working">How to test if it is working</h2><p>You can use cURL to test this snippet is working, you need to pass a debug header <code>Fastly-Debug</code> in order to see this:</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">curl -sLIXGET https://www.example.com/ -H &apos;Fastly-Debug:1&apos; | grep -i surrogate-key
surrogate-key: www.example.com</code></pre><figcaption><code>Surrogate-Key</code> when there are no existing cache tags to append to</figcaption></figure><p>If you have existing cache tags in the response, then you should see the custom cache tag appended at the end:</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">curl -sLIXGET https://www.exampletwo.com/ -H &apos;Fastly-Debug:1&apos; | grep -i surrogate-key
surrogate-key: aZw7 f57P eygW 45lo gq5V Czwh cvlF /+/A 7KwZ eth0 www.exampletwo.com</code></pre><figcaption><code>Surrogate-Key</code> when there are no existing cache tags to append to</figcaption></figure><h2 id="how-to-purge-an-entire-domain-using-the-fastly-api">How to purge an entire domain using the Fastly API</h2><p>Now armed with the knowledge that every single cached object in Fastly has a custom cache tag attached to it (being the domain) we can easily purge a single domain using the Fastly API:</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">curl -sXPOST https://api.fastly.com/service/SERVICE_ID/purge/www.example.com -H &apos;Fastly-Soft-Purge:1&apos; -H &quot;Fastly-Key:$FASTLY_TOKEN&quot; | jq
{
  &quot;status&quot;: &quot;ok&quot;,
  &quot;id&quot;: &quot;10427-1615460322-6&quot;
}</code></pre><figcaption>Purge a <code>Surrogate-Key</code> using the Fastly API.</figcaption></figure><p><strong>N.B.</strong> this is executing a &apos;Soft Purge&apos;, so the object is still in Fastly cache, it is just marked as stale. This is useful if you have configured Serve While Stale (SWS), as stale objects can be served in the event the origin is down or otherwise not returning happy responses. So this is a much safer purge. If you want to read more about this, see my blog post on <a href="https://www.pixelite.co.nz/article/fastly-soft-purge-vs-instant-purge/">Soft Purge vs Instant Purge</a>.</p><h2 id="further-thoughts">Further thoughts</h2><p>I did ask Fastly if they would consider adding another option to the purge dropdown (my attempt at a mockup below), without having to muck around with custom VCL snippets.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.pixelite.co.nz/content/images/2021/03/image--23-.png" class="kg-image" alt="Purge a single domain and it&apos;s content from a Fastly service with multiple domains" loading="lazy" width="538" height="522"><figcaption>Mockup for purging by domain in the Fastly UI</figcaption></figure><p>Apparently this is not a popular feature request. If this blog post was helpful for you, I would encourage you to contact Fastly to let them know this would make your life easier.</p><p>Stock Varnish can <code>BAN</code> by domain, so this is not a limitation of Varnish.</p><h2 id="comments">Comments</h2><p>If this has helped you, or you have an improvement to the VCL, please let me know in the comments.</p>]]></content:encoded></item></channel></rss>