Skip to main content

Blog

Miscellaneous articles relating to the use and development of Tuskfish CMS.

Copyright, all rights reserved.

In this collection

Version 2.3.4 released: Inline galleries!

A small feature release: 

  • Collections now have access to a 'Gallery' template option, which displays child objects as a thumbnail grid below the description. This allows you to use collections to generate an 'article + gallery' pattern for (say) events or travel articles, or can be used as an alternative display format for publications catalogues etc.

Tuskfish v2.3.3 release: CSS grid themes

A small feature release and polish. The shiny new things are:

  • CSS grid themes: Several new themes are available based on native CSS grid, which removes Bootstrap as a dependency. This makes them both simpler and more lightweight. They are also more refined and precise in terms of spacing and layout. The new themes are Tide (teal), Sand and Sea (blue), Kelp (olive-amber), Canyon (rust stained) and Pond (green). The themes feature a 'night mode' toggle and use image source sets to ensure that thumbnails are crisp when displayed at different resolutions or on high resolution screens.
  • Block template overrides: Copy the internal block templates from modules into the relevant theme/blocks directory and customise them however you like, (eg. copy yourmodule/Block/some-template.html to themes/yourtheme/blocks/some-template.html). The versions internal to the modules provide safe defaults if theme-specific versions are not available.
  • Pagination control has been extracted from the PHP code and placed in a html template.

Faster thumbnail generation: Optional support for libvips

I've added libvips as a third option for thumbnail generation. It's better than ImageMagick in that it is around 4x faster, and uses 1/10th the memory. It is particularly good if your webserver has more than one core available as it will split the work across multiple threads, whereas ImageMagick won't. Like ImageMagick, it is colour space aware (GD isn't).

In terms of speed and quality, the options are now (best to worst):

  • libvips
  • ImageMagick
  • GD (default)

GD is best avoided (worst quality, and the lack of colour space awareness can make images drab and lifeless) but is set as default because it is widely available on most web hosts, whereas ImageMagick is sometimes available, usually with some configuration, and libvips usually isn't a PHP thing. You'll probably need your own VPS or webserver, or be running Tuskfish on a Docker stack.

To enable it, install libvips-tools on your system and rename ResizeImage-Vips.php to ResizeImage.php (and backup or remove the existing one). Refer to the files's docblock for instructions, and to configure compression and sharpening.

Sharpening is off by default, but I have found a value of 0.5 (light sharpening) works pretty well. It comes at a cost though, forcing a second pass over the image so it noticeably slows thumbnail generation down on pages with a lot of images. However, this is only an issue on first load, as after that you're pulling the thumbnails from the cache.

Tuskfish 2.3.2 released

A small update to simplify / reduce friction during installation:

  • Change index.php to route from request path only, independent of scheme and host. This avoids the need to manually configure when deploying behind reverse proxy that handles TLS termination.

Tuskfish 2.3.1 released

A minor update that provides a performance boost, file structure reorganisation and security hardening:

  • Optimise database performance: Indexes have been added, which accelerate queries, improve resilience under load, and further reduce the already small hardware requirements of Tuskfish. The indexes are primarily of benefit to sites with large content loads.
  • Modularity has been improved so that new modules can be dropped in or ripped out without modifying other code, which will simplify future development.
  • The SMTP password is now encrypted at rest.

Tuskfish next steps

Whenever I think Tuskfish is 'finished' some new ideas will hit me. I do think we are in the endgame now, here is what's planned:

  • Optimise database performance [done]: Add indexes and improve a couple of query issues. This is being tested on my largest production site, which has > 1,000 articles. Page execution times, which were already fast, have been halved, with individual content pages now down to 5-8 ms, while the index page (heaviest) is down to 10-12 ms. Practically speaking, you probably won't notice any difference unless you have a massive site. But halving execution times means your site can handle (nearly) double the number of requests and makes it a lot more resilient under load.
  • Complete modularisation [done]: I would like it to be able to add new functionality by dropping a single directory into the system and not having to touch anything else. It's mostly there but there are still a couple of things you need to manually wire in (module header, templates).
  • Dockerise Tuskfish: Create a Docker Compose stack and Makefile that will let you deploy Tuskfish with a single command. I've done this for a couple of other projects recently. Imagine typing 'make up' and the whole system just builds itself and deploys inclusive of self-updating TLS certificates.

Then, I think I need to go work on something else for a while.

Introducing Go2Serve: a lightweight static file server

Fast, lightweight, MIT licensed. Go for it!
Fast, lightweight, MIT licensed. Go for it!

I'm releasing Go2Serve, a static file server written in Go. It's a single binary with simple configuration, no runtime dependencies, and secure defaults out of the box. You can build it and learn how to use it in two minutes.

I built Go2Serve because I wanted something lightweight, performant and safe. Something I could drop onto a Pi or a VPS, point at a directory, and have it serving files over HTTPS in under a minute with near-zero configuration.

Go2Serve serves static files. That's it. No CGI, no reverse proxying, no dynamic content. What it does do, it tries to do well:

  • HTTPS with zero configuration: Pass `--domain example.com` and go2serve handles Let's Encrypt certificates automatically, including renewal. Manual certificates are also supported, with automatic reload every 60 seconds for zero-downtime rotation.
  • Security defaults: Path traversal protection (including via symlinks), `X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy`, and optional HSTS and Content-Security-Policy headers. These are on by default, not buried in a config file you have to remember to write.
  • Per-IP rate limiting: Token bucket rate limiting is enabled out of the box, with proxy-aware client IP extraction when you're behind a load balancer.
  • Lightweight: No CGO, no runtime dependencies. The Docker image is built from `scratch` and contains nothing but the binary and CA certificates. Memory footprint is minimal.

Go2Serve ships Docker-first. The supported, cross-platform install path is "make up", which builds a pinned, reproducible Linux image and runs it in a hardened scratch container — identical on Linux, macOS, and Windows (WSL2). Building a native binary (make build-bin, requires Go) is offered as a secondary path for non-Docker hosts. See the README.md!

Quality and security of AI-generated code: Thoughts on a process

TLDR: You can get great results but it requires a structured review process. If you are just "vibe coding"  you are building a bomb that is going to go off in your face.

I've been experimenting with AI code generation for a side project written in Golang. The project has been implemented by Opus 4.6 (Claude Code) under my direction. This is the first time I've used Golang so I'm pretty slow and can't scrutinise the output as thoroughly as I could PHP. I've been thinking a lot about security. Are there processes we can follow to reduce risk, when working with machine-generated code? I think so. My high-level process has been to:

  • Have a discussion with the model about a feature or a change, to identify a good approach. It often comes up with better ideas or refinements.
  • Explicitly ask for an implementation plan, causing the model to break up the problem into a structured series of small steps, which I sanity check (read) and adjust.
  • Ask the model to implement the plan (if it is complex, perhaps one phase at a time).
  • Manually check that the change functions as expected.
  • Explicitly ask the model to review the changes and evaluate if it is a robust solution (repeat if necessary).

This process works well for two reasons. Firstly, it breaks up the work into small, carefully scoped chunks that fit within the model's context window, keeping it focussed. Secondly, the review aspects (the manual check, and instruction to critically review the work) removes a lot of bugs, so you maintain a solid foundation to work from. Most of the time Opus will find a few bugs in its implementation, if you ask it to check, and it may take two or three rounds before it stops finding problems.

A few thoughts on Golang vs PHP for web development

I have a new project nearing completion which is based on Golang, and with a Postgres backend. TLDR I wanted to add a compiled language to my skillset so that I could produce fast binary executables.

I settled on Golang because it is modern, memory safe (mostly) and provides highly efficient built in webserver functionality. Goroutines have a tiny memory footprint, fast start up time, and low CPU overhead, all from a single small compiled binary. Compared to Apache2 with its endless configuration options and complexity, it's quite a relief to deal with.

And Golang has not disappointed me. The efficiency gains are real and will allow me to deploy onto minimal hardware, thereby directly saving money. Even on a Raspberry Pi 5 development box (yes, really) my web app runs like lightning and has shockingly low CPU and memory footprints.

But there is a downside, and this is where PHP has the advantage: Maintenance. If a PHP site has a problem you can often login while it's running, poke around a bit and fix it, without much concern that you will torch the entire system. The files are human readable text, so modifying one or reverting a bad change is basically instant with limited blast radius. You can do emergency maintenance on the road from a tablet or even a phone.

Claude's crazy token burn has NOT been fixed stop apologising for them

Around the end of March there were widespread reports of a sudden jump in token consumption by Claude Code, mainly with Opus. People started burning through their usage limits in minutes, when previously they had hours.

This wasn't a problem for me, but I heeded the 'mitigation' advice and removed all plugins, skills, agents, and MCPs to minimise context injection. I also audited my configuration using the Context Audit skill you can download from Brad | AI Automation.

Around mid-April Anthropic claimed to have fixed it. Well, no. They haven't. I started experiencing the problem as soon as my usage reset and I had access to Opus 4.7, even though I reduced the effort to 'medium' from the default 'xHigh'.

It's terrible! Previously I could carefully steward my session limit through two or three hours of code work with Opus. Today? About 30 minutes and with a far smaller volume of work achieved.

Don't use Rode Connect to transfer files from the Rode Wireless Pro

It's insanely slow, ridiculously so. To get the files off quickly, just mount the transmitters as storage (Mac) and drag and drop the files onto your desktop. It's literally hundreds of times faster. If you plug the case into your computer with the transmitters inserted, they will mount automatically. (I presume that on Windows you can just open them as storage through File Explorer).

Review: S2Pi Aluminum NAS case with Ice Tower cooler

Replaced the double USB connector with a short male to male cable.
Replaced the double USB connector with a short male to male cable.

TLDR: Recommended for Raspberry Pi 4b...if you don't have issues with the USB connector (mine seems defective, which is a possible dealbreaker). Excellent construction but fan is noisy at high loads; can mitigate with an improved fan control script (provided). The S2Pi Aluminum NAS case provides a rugged housing for the Raspberry Pi 4b with M.2 SSD storage and an Ice Tower heat sink for strong cooling performance. It's an excellent package for upgrading your Pi to a lightweight server.

I have developed an improved fan speed control script that turns the fan off when not needed, and ramps with CPU temperature. Available for download within.

Testing Starlink internet from a Qatar Airways flight

Well, it works great. Very cool. And free, yay!

Raspberry Pi 5 + PCIe SSD: A Legitimate Server Platform

Pi 5 with 16 GB RAM, 1 TB Samsung 990 Pro M.2 SSD, and Geekworm P580 PCIe case.
Pi 5 with 16 GB RAM, 1 TB Samsung 990 Pro M.2 SSD, and Geekworm P580 PCIe case.

I recently got dunked on for saying the Raspberry Pi 5 makes a great home lab server if you equip it with an SSD drive. And I don't really blame the guy, because until the Pi 4b, they were pretty awful, and for the 3B and below you were stuck with running the OS from a microSD card. His mental model was probably stuck somewhere around there.

The Pi 5 is a huge level up in performance, especially once you add SSD storage via its PCIe slot.

Tuskfish 2.2.7 released

A minor patch to fix a bug in collection pagination.

Tuskfish 2.2.6 released

A minor maintenance release to harden the WebAuthn service class.

Tuskfish 2.2.5 released

A minor patch:

Tighten access-control-origin header, close open redirects, improve validation and add resource limits to JS.

Tuskfish 2.2.4 released: FIDO2 / WebAuthn login

Tuskfish CMS now supports login with Windows Hello, Touch ID, Face ID, iOS and Android devices, and hardware security keys like the Yubikey. This is implemented as a second factor after password check for two-factor authentication (2FA) login security.

Users can register and revoke their 2FA credentials by visiting Preferences => Two-Factor Authentication in the Admin panel. Registration of a credential only takes a few seconds. Once a credential is registered, two-factor login becomes mandatory, so it is a good idea to register more than one device to avoid lock out. Users can revert to simple password login by revoking all their 2FA credentials.

Technically this is an implementation of the FIDO 2 / WebAuthn standards. Users register passkeys from platform authenticators (Hello, Touch ID etc) or hardware security keys (CTAP2 authenticators). Credentials are public-key based and origin-bound; no shared secrets are stored.

Tuskfish 2.2.3 released

Minor bugfixes:

Reverted cache writes to avoid use of remove(), as this function is normally disabled in php.ini; tidied cached file names (prevent param separator being used on first param); fixed bug in gallery logic that prevented dynamic changes in columns to suit display width.

Tuskfish 2.2.2 released

Minor cosmetic improvements and bugfixes: Changed radio button controls to coloured toggle switches; removed deprecated / redundant curl_close() calls; corrected some type initialisation and return values errors; and fixed bug making custom RSS feeds carry generic site title/description.