# AK//Journal > Code, Systems, Brain Logs Language: en URL: https://journal.alsonkaw.com/ All pages on this site are available as clean Markdown by adding the header `Accept: text/markdown` to any HTTP request. REST API: https://journal.alsonkaw.com/wp-json/mescio-for-agents/v1/markdown?url={page_url} ## Pages - [Privacy Policy](https://journal.alsonkaw.com/privacy-policy/): Privacy Policy Last updated: March 10, 2026This website (“the site”) is a personal journal and blog. I respect your privacy and keep data collection to a minimum. What information I collect Analytics (Plausible) I use Plausible Analytics, a privacy-focused analytics - [Home](https://journal.alsonkaw.com/): Hello there This journal micro-blog is my working log: short notes on what I’m learning, building, and debugging. Why a micro-blog? Latest Notes - [Style Guide](https://journal.alsonkaw.com/style-guide/): Style Guide This is a style guide page. Images Headings Heading 1 — Display type Heading 2 — Section header Heading 3 — Subsection Heading 4 — Minor header Paragraph This is body text. It should be readable, calm, and - [Topics](https://journal.alsonkaw.com/topics/): Browse posts by domain and topic Image AI Security Docker Authentication Privacy Prompting Localhost Commit Passkey Security Performance DevTools Git WordPress Nextcloud - [Now](https://journal.alsonkaw.com/now/): Now This page is a snapshot of what I’m focused on right now—updated occasionally when priorities shift. Current Focus Building and optimizing Ash+Zelkova — a digital agency based in Singapore focused on web and graphics.Improving my systems and workflows: shipping - [About](https://journal.alsonkaw.com/about/): About This journal micro-blog is my working log: short notes on what I’m learning, building, and debugging. It’s meant to be searchable, linkable, and useful to future-me — a brain log of things I don’t want to forget.This is where ## Blog Posts - [Makefile for Deployments and Dev Workflows](https://journal.alsonkaw.com/makefile-for-deployments-and-dev-workflows/) (2026-05-14): A single Makefile replaces scattered shell scripts, reads .env to switch environments, and gives every project a consistent command interface. - [HP Turned Printing Into a Subscription](https://journal.alsonkaw.com/hp-turned-printing-into-a-subscription/) (2026-05-12): HP's All-In Plan charges a monthly fee for your printer, ink, and paper. Cancel after 30 days and you pay a fee. Here's what you're actually signing up for. - [Chrome Put a 4GB AI Model on Your Mac](https://journal.alsonkaw.com/chrome-put-a-4gb-ai-model-on-your-mac/) (2026-05-09): Google Chrome silently downloads a 4GB Gemini Nano AI model onto your Mac or PC without consent. Here's how to find it, delete it, and stop it coming back. - [Windows 11 Local Account: The Fast Way](https://journal.alsonkaw.com/windows-11-local-account-the-fast-way/) (2026-05-06): How to set up Windows 11 with a local account using start ms-cxh:localonly — faster than bypassnro, works on 24H2 and 25H2, and takes about 30 seconds. - [CVE-2026-31431: Update Your Kernel Now](https://journal.alsonkaw.com/cve-2026-31431-update-your-linux-kernel-now/) (2026-05-02): CVE-2026-31431 "Copy Fail" is a Linux kernel LPE affecting every major distro since 2017. A 732-byte Python script gets root with no race conditions. Patch now. - [AI Didn’t Go Rogue, You Handed It the Keys](https://journal.alsonkaw.com/ai-didnt-go-rogue-you-just-handed-it-the-keys/) (2026-04-30): A Cursor agent wiped a company's production database in 9 seconds. The AI didn't go rogue — the owner handed it root access and stored backups on the same volume. - [What if your password manager stored nothing](https://journal.alsonkaw.com/what-if-your-password-manager-stored-nothing/) (2026-04-20): HIPPO is a proposed storeless password manager — no vault, no stored passwords, just cryptographic computation on demand. Interesting tradeoffs. - [WebinarTV turned your private meeting into content](https://journal.alsonkaw.com/webinartv-turned-your-private-meeting-into-content/) (2026-04-18): A company has been crawling Zoom for meeting links, sending fake attendees in to record, then turning it all into AI podcasts. Full writeup on the main blog. - [Claude feels dumber lately — here’s the fix](https://journal.alsonkaw.com/claude-feels-dumber-lately-heres-the-fix/) (2026-04-13): Claude's chat responses got noticeably worse — turns out it's a default effort setting change. Custom instructions fixed it. - [portless | Named .localhost URLs for Development](https://journal.alsonkaw.com/portless-named-localhost-urls-for-development/) (2026-03-23): Portless replaces localhost ports with stable name.localhost URLs, adds optional HTTPS, supports subdomains and git worktrees, and auto-assigns ports. - [Security hardening for WordPress](https://journal.alsonkaw.com/security-hardening-for-wordpress/) (2026-03-15): A practical walkthrough of how I hardened my WordPress setup with passkeys / 2FA, Cloudflare Access, security headers, CSP tuning, and user enum fixes. - [Extending Official Docker Images with Dockerfile](https://journal.alsonkaw.com/extending-official-docker-images-with-dockerfile/) (2026-03-09): Learn how to extend official Docker images with a Dockerfile—example adds FFmpeg to Nextcloud for video thumbnails and previews. - [How Passkeys Work](https://journal.alsonkaw.com/how-passkeys-work/) (2026-03-04): A quick takeaway on how passkeys work—what they are, why they’re safer than passwords, and what to know when adopting them. - [Nextcloud felt sluggish, MariaDB was the bottleneck](https://journal.alsonkaw.com/nextcloud-felt-sluggish-mariadb-was-the-bottleneck-not-php-redis/) (2026-03-02): Nextcloud lagged because MariaDB was maxed—InnoDB tuning + repairs + throttling fixed it. - [How to Write Better Git Commit Messages](https://journal.alsonkaw.com/how-to-write-better-git-commit-messages/) (2026-02-27): A concise guide to writing better Git commit messages—clear subjects, useful context, and patterns that make history easier to read and review. - [Migrating a Docker image to an air-gapped VM](https://journal.alsonkaw.com/migrating-a-docker-image-to-an-air-gapped-vm/) (2026-02-27): Step-by-step guide to migrating Docker images to an air-gapped VM using docker save/load, plus tips for tagging, updates, and rollback. --- # Full Content --- title: "Makefile for Deployments and Dev Workflows" url: "https://journal.alsonkaw.com/makefile-for-deployments-and-dev-workflows/" lang: "en-US" type: "post" description: "A single Makefile replaces scattered shell scripts, reads .env to switch environments, and gives every project a consistent command interface." last_modified: "2026-05-14T07:11:17+00:00" categories: [Notes] --- # Makefile for Deployments and Dev Workflows At some point every project grows a collection of shell scripts. `deploy.sh`, `restart.sh`, `build-prod.sh`, `setup-dev.sh`. They accumulate. You forget what half of them do. You forget which ones need arguments. Someone else on the project has no idea where to start.A Makefile solves this. One file, one interface, every command in one place. Type `make` and get a list of everything available. Type `make deploy` and it runs. No documentation required. ### What a Makefile Actually Is Make is a build automation tool that has shipped with Unix systems since 1976. It was designed for compiling C code but works just as well for running any sequence of shell commands. A Makefile is a plain text file named `Makefile` (capital M, no extension) that lives in your project root. The basic structure is a **target**, followed by a colon, followed by optional **dependencies**, followed by indented **commands**: makefile BashCopy target: dependency command to run Code language: Bash (bash)The indentation must be a real tab character, not spaces. That is the one syntax rule that catches everyone the first time. ### The Simplest Useful Makefile makefile BashCopy .PHONY: up down restart logs up: docker compose up -d down: docker compose down restart: docker compose down && docker compose up -d logs: docker compose logs -f Code language: Bash (bash)Run `make up`, `make down`, `make logs`. No more typing `docker compose` every time. The `.PHONY` declaration at the top tells Make that these targets are not filenames — they are just named commands. Without it, if you ever happen to have a file called `up` or `logs` in the same directory, Make would see that the file exists and decide there is nothing to do. ### Self-Documenting Help Target The first target in a Makefile is the default — running `make` with no arguments runs it. Use that slot for a `help` target that prints every available command: makefile BashCopy .DEFAULT_GOAL := help .PHONY: help up down restart deploy help: ## Show available commands @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / \ {printf " %-15s %s\n", $$1, $$2}' $(MAKEFILE_LIST) up: ## Start all containers docker compose up -d down: ## Stop all containers docker compose down deploy: ## Deploy to active environment ./scripts/deploy.sh Code language: Bash (bash)The `##` comment after each target is what the `awk` command parses and prints. Output looks like: BashCopy help Show available commands up Start all containers down Stop all containers deploy Deploy to active environment Code language: Bash (bash)Every new target you add with a `##` comment appears automatically. No separate documentation to maintain. ### Reading from .env This is where Makefiles become genuinely useful for multi-environment setups. Add these two lines at the top of your Makefile: makefile BashCopy include .env export Code language: Bash (bash)Every variable in your `.env` file is now available as a Make variable and also exported into the environment of every command that runs. A `.env` like this: BashCopy ENV=staging DEPLOY_HOST=user@staging.myserver.com DEPLOY_PATH=/var/www/staging Code language: Bash (bash)Means your Makefile can do this: makefile BashCopy deploy: ## Deploy to $(ENV) @echo "Deploying to $(ENV)..." rsync -avz --delete ./dist/ $(DEPLOY_HOST):$(DEPLOY_PATH) ssh $(DEPLOY_HOST) "cd $(DEPLOY_PATH) && docker compose up -d --build" Code language: Bash (bash)Change `DEPLOY_HOST` and `DEPLOY_PATH`, run `make deploy`, and it deploys to the production server and path instead. Same command, different behaviour, driven entirely by the environment file you already maintain. ### Environment-Conditional Logic For more complex branching you can use Make’s `ifeq` blocks at the top level: makefile BashCopy include .env export ifeq ($(ENV),prod) COMPOSE_FILE := docker-compose.prod.yml DEPLOY_HOST := user@prod.myserver.com DEPLOY_PATH := /var/www/production else COMPOSE_FILE := docker-compose.staging.yml DEPLOY_HOST := user@staging.myserver.com DEPLOY_PATH := /var/www/staging endif Code language: Bash (bash)Now `$(COMPOSE_FILE)`, `$(DEPLOY_HOST)`, and `$(DEPLOY_PATH)` resolve to the right values for whichever environment `.env` specifies, and every target uses them without any conditional logic inside the targets themselves. ### Guards: Failing Fast on Missing Variables If a required variable is not set, you want to know immediately — not halfway through a deployment. Add a guard target: makefile BashCopy guard-%: @[ -n "$($(*))" ] || (echo "ERROR: $* is not set"; exit 1) deploy: guard-ENV guard-DEPLOY_HOST ## Deploy to active environment @echo "Deploying to $(ENV) at $(DEPLOY_HOST)..." Code language: Bash (bash)The `guard-%` pattern matches any target that starts with `guard-`. Running `make deploy` first checks that `ENV` and `DEPLOY_HOST` are both set. If either is empty, it prints an error and exits before any deployment command runs. ### A Real-World Example Here is a condensed version of a Makefile for a Docker-based WordPress deployment: makefile BashCopy include .env export ifeq ($(ENV),prod) COMPOSE_FILE := docker-compose.prod.yml DEPLOY_HOST := $(PROD_HOST) DEPLOY_PATH := /var/www/production else COMPOSE_FILE := docker-compose.staging.yml DEPLOY_HOST := $(STAGING_HOST) DEPLOY_PATH := /var/www/staging endif .DEFAULT_GOAL := help .PHONY: help up down restart deploy pull logs shell db-backup guard-% help: ## Show available commands @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / \ {printf " %-15s %s\n", $$1, $$2}' $(MAKEFILE_LIST) up: ## Start containers docker compose -f $(COMPOSE_FILE) up -d down: ## Stop containers docker compose -f $(COMPOSE_FILE) down restart: down up ## Restart containers pull: ## Pull latest images docker compose -f $(COMPOSE_FILE) pull deploy: guard-ENV guard-DEPLOY_HOST ## Sync files and restart on $(ENV) rsync -avz --delete --exclude='.env' \ ./ $(DEPLOY_HOST):$(DEPLOY_PATH) ssh $(DEPLOY_HOST) "cd $(DEPLOY_PATH) && make up" logs: ## Tail container logs docker compose -f $(COMPOSE_FILE) logs -f shell: ## Open shell in app container docker compose -f $(COMPOSE_FILE) exec app sh db-backup: ## Dump database to ./backups @mkdir -p ./backups docker compose -f $(COMPOSE_FILE) exec db \ mysqldump -u$(DB_USER) -p$(DB_PASS) $(DB_NAME) \ > ./backups/$(DB_NAME)-$(shell date +%Y%m%d-%H%M%S).sql @echo "Backup saved to ./backups/" guard-%: @[ -n "$($(*))" ] || (echo "ERROR: $* is not set"; exit 1) Code language: Bash (bash)`make deploy` on staging, change one line in `.env`, `make deploy` on production. `make db-backup` dumps the database with a timestamped filename. `make shell` drops you into the container. All of it is visible from `make help` without reading a single line of documentation. ### The @ Prefix One thing worth knowing: Make prints every command before running it by default. Prefix a command with `@` to suppress that. Most targets benefit from `@echo` messages replacing the raw command output, especially in CI environments where clean output matters. ### Why This Beats Shell Scripts A folder of shell scripts has no standard interface. `make help` always works the same way regardless of the project. Dependencies between targets are explicit — `restart: down up` means restart always runs down then up in order, and you can see that at a glance. Variables flow through consistently. And because Makefiles are text files in your repo, they version-control alongside everything else. The full [GNU Make documentation](https://www.gnu.org/software/make/manual/make.html) covers everything from pattern rules to parallel execution if you want to go deeper. For most deployment and ops workflows, the patterns above are all you need. --- --- title: "HP Turned Printing Into a Subscription" url: "https://journal.alsonkaw.com/hp-turned-printing-into-a-subscription/" lang: "en-US" type: "post" description: "HP's All-In Plan charges a monthly fee for your printer, ink, and paper. Cancel after 30 days and you pay a fee. Here's what you're actually signing up for." last_modified: "2026-05-12T04:35:56+00:00" categories: [Notes] --- # HP Turned Printing Into a Subscription A TikTok by [@jackiesevna](https://www.tiktok.com/@jackiesevna/video/7635422458077596936) went viral recently after she discovered her HP printer was charging her a monthly subscription fee just to print. As [Cybernews covered](https://cybernews.com/tech/hp-printer-subscription/), the comments filled up fast — turns out a lot of people had no idea this was happening. The outrage is understandable, but the full picture is worth understanding before drawing conclusions. ### Two Different Subscriptions, Two Very Different Situations HP runs two models that often get conflated. **HP Instant Ink** is an ink-only subscription. Your printer connects to HP’s servers over Wi-Fi, monitors its own cartridge levels, and automatically ships replacement ink before you run out. Plans start at around $1.79 a month for 15 pages and scale up from there. You pay per page, not per cartridge. **HP All-In Plan** is a full printer-as-a-service model. You do not buy the printer — you lease it. The monthly fee covers the hardware, ink, and support. Plans start from around $6.99 a month for 20 pages on the entry-level HP Envy, up to $35.99 a month for 700 pages on a business tier. Cancel after the 30-day grace period and you pay a cancellation fee to exit. The printer goes back. These are meaningfully different. One is an ink delivery service bolted onto a printer you own. The other means you never owned the printer to begin with. ### What Jackie’s Video May Have Got Wrong Jackie mentioned she purchased her printer and was then being charged a subscription on top of that purchase. If that’s accurate, she is almost certainly on HP Instant Ink, not the All-In Plan — because the All-In Plan **does not** involve buying a printer at all. It is a lease from the start. The important clarification: cancelling HP Instant Ink does not permanently lock your printer. According to HP’s own support documentation, the Instant Ink cartridges stop working at the end of the billing cycle — but the printer itself continues to function normally once you replace those cartridges with regular retail ones you purchase separately. You are not locked out of printing forever. You just cannot keep using the subscription-specific cartridges that HP sent you, since those are leased alongside the ink plan. What likely happened to Jackie, and to many others in the comments, is that they enrolled in HP Instant Ink during printer setup — often as part of a promotional offer — received HP’s subscription cartridges in the box or by post, and did not fully register that those cartridges only work while the subscription is active. Cancelling the plan means replacing those cartridges with ones you buy outright. That is still a frustrating design. But it is different from your printer being held hostage. ### Where It Gets Genuinely Worse The murkier situation is HP+. If you activated HP+ during setup — HP’s free tier that offers cloud printing and extended warranty features — your printer becomes permanently locked to genuine HP cartridges only. Cancel Instant Ink, and you can still print. But you can never use cheaper third-party or remanufactured cartridges on that machine, ever. That restriction does not go away. For the All-In Plan, the lock-in is the contract itself. Miss the 30-day cancellation window and you are committed until you pay your way out. ### What to Check If You Own an HP Printer Log into your HP account and look under active subscriptions. If you are on Instant Ink and want out, cancel through the HP Instant Ink website — not through your bank — then buy a standard set of retail cartridges before your billing cycle ends. The printer will work fine once those are installed. If you are on the All-In Plan and within 30 days of signing up, cancel now. After that, read the terms carefully before acting — the cancellation fee depends on how long you have been enrolled and which tier you are on. And if you are setting up a new HP printer: read every screen during the setup flow. The Instant Ink enrolment step is presented as part of the normal process. It is optional. --- --- title: "Chrome Put a 4GB AI Model on Your Mac" url: "https://journal.alsonkaw.com/chrome-put-a-4gb-ai-model-on-your-mac/" lang: "en-US" type: "post" description: "Google Chrome silently downloads a 4GB Gemini Nano AI model onto your Mac or PC without consent. Here's how to find it, delete it, and stop it coming back." last_modified: "2026-05-11T14:38:25+00:00" categories: [Notes] --- # Chrome Put a 4GB AI Model on Your Mac Open Finder, press `Cmd + Shift + G`, paste `~/Library/Application Support/Google/Chrome/` and look for a folder called `OptGuideOnDeviceModel`. If it’s there, it’s probably around 4GB. Chrome put it there without asking.The file inside is named `weights.bin` — the weights for Gemini Nano, Google’s on-device large language model. Before any AI feature surfaces in the UI, Chrome quietly reads your GPU and unified memory to decide if your hardware qualifies, then downloads the full payload in the background. No prompt. No notification. No opt-in. ### What It Actually Does Google’s official response: Gemini Nano powers scam detection and developer APIs without sending data to the cloud. That framing is technically accurate but misses the point. The AI Mode pill in Chrome’s omnibox routes queries to cloud servers — the local model has nothing to do with that. It powers features like “Help me write.” So most users have 4GB sitting on their drive for a writing assistant they have probably never opened. Security researcher Alexander Hanff documented the behaviour in detail on [his blog](https://www.thatprivacyguy.com/blog/chrome-silent-nano-install/), running a controlled test on a fresh Chrome profile on macOS using the OS’s own filesystem event logs. The browser created the model directory and downloaded the full payload during idle browsing time, completing in just over fourteen minutes with no human interaction. ### How to Check If It’s There **Mac:** - Open Finder → `Cmd + Shift + G` - Paste: `~/Library/Application Support/Google/Chrome/` - Look for `OptGuideOnDeviceModel` **Windows:** - `Win + R` - Paste: `%LOCALAPPDATA%\Google\Chrome\User Data\` - Look for `OptGuideOnDeviceModel` ### How to Delete It and Stop It Coming Back Deleting the folder directly does nothing permanent — Chrome reinstalls it the next chance it gets. You need to disable the feature before touching the files. **Quickest method — Mac and Windows:** Go to `chrome://settings/system` and toggle **On-device AI** off. On Mac, `weights.bin` disappears immediately. Then delete `OptGuideOnDeviceModel` to recover the space. [PCWorld confirmed this works](https://www.pcworld.com/article/3132803/chrome-silently-downloads-a-4gb-ai-model-heres-how-to-remove-it.html) on both platforms. **If the settings toggle is not visible:** Go to `chrome://flags` and disable both flags: - `#optimization-guide-on-device-model` - `#prompt-api-for-gemini-nano` Click Relaunch, then delete `OptGuideOnDeviceModel` with Chrome fully closed. Full steps with screenshots at [Android Authority](https://www.androidauthority.com/how-to-disable-chrome-gemini-nano-delete-weights-bin-3665109/). **Windows only — permanent registry policy:** Navigate to `HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome`, create a DWORD called `GenAILocalFoundationalModelSettings` and set it to `1`. Chrome respects enterprise policies and will not override this with updates, making it the most reliable long-term fix on Windows. ### The Bigger Picture This is not new behaviour. Reports of Chrome downloading the model appeared as early as April 2025 when it weighed around 3GB. By November 2025 it had grown to 4GB. The direction is clear. The on-device argument — that local processing is better for privacy — is not wrong in principle. But a privacy benefit does not waive the requirement to ask. If keeping AI processing local was meant to earn user trust, silently consuming 4GB of someone’s disk is a strange way to go about it. --- --- title: "Windows 11 Local Account: The Fast Way" url: "https://journal.alsonkaw.com/windows-11-local-account-the-fast-way/" lang: "en-US" type: "post" description: "How to set up Windows 11 with a local account using start ms-cxh:localonly — faster than bypassnro, works on 24H2 and 25H2, and takes about 30 seconds." last_modified: "2026-05-11T14:38:39+00:00" categories: [Notes] --- # Windows 11 Local Account: The Fast Way Every laptop that lands on my desk gets set up offline. No Microsoft account, no cloud sync, no OneDrive prompt on first boot. Windows 11 has made this progressively harder to do, but there is still a fast, clean way to get it done. ### Why Local Accounts Still Matter A local account keeps the machine independent. The user’s login is not tied to a Microsoft account, no settings sync happens without being explicitly configured, and OneDrive does not auto-enable. For work deployments this matters — you want a clean baseline, not a machine that immediately starts pulling down the previous user’s cloud state. Microsoft has been quietly tightening the OOBE (Out-of-Box Experience) to push account sign-in as the only path. The well-known `oobe\bypassnro` workaround has been officially removed from newer builds, which is what led most people to notice the change. The replacement is faster anyway. ### The Command That Actually Works During Windows 11 setup, when you hit the screen asking you to connect to a network or sign in with a Microsoft account: - Press `Shift + F10` to open a command prompt - Type the following and press Enter: PowershellCopy start ms-cxh:localonly Code language: PowerShell (powershell)A local account creation window appears. Fill in username and password, click Next, and setup continues straight to the desktop. No reboot required, unlike the old `bypassnro` method. The full flow is: choose region and keyboard layout, skip Wi-Fi, open the command prompt with `Shift + F10`, run the command, set your username and password, and decline the telemetry prompts. From command prompt to desktop takes about 30 seconds. ### What I Use for the USB I use [YUMI Multiboot](https://www.pendrivelinux.com/yumi-multiboot-bootable-usb-creator/) to manage my bootable USB. It supports multiple ISOs on a single drive, so the same stick carries the Windows 11 installer, a Linux live environment, and a diagnostics tool. The Windows 11 ISO itself comes from the [official Media Creation Tool](https://www.microsoft.com/software-download/windows11) — always the latest build, which matters because older ISOs may behave differently during OOBE. ### How Long This Will Last The honest answer: probably not forever. Microsoft has already been testing removal of `ms-cxh:localonly` in Insider preview builds, with some October 2025 builds looping OOBE back to the Microsoft account gate when the command is run. Those changes have not hit mainstream release builds yet, but the direction is clear. If permanent offline-first deployment matters for your environment, the longer-term solution is a documented offline deployment strategy — `unattend.xml` answer files or a pre-configured image rather than relying on OOBE bypass commands that Microsoft can remove in any update. For now, `start ms-cxh:localonly` works on every release build I have tested, including 24H2 and 25H2. It is the fastest method currently available and a direct replacement for `bypassnro` on any build where that script has been removed. --- --- title: "CVE-2026-31431: Update Your Kernel Now" url: "https://journal.alsonkaw.com/cve-2026-31431-update-your-linux-kernel-now/" lang: "en-US" type: "post" description: "CVE-2026-31431 \"Copy Fail\" is a Linux kernel LPE affecting every major distro since 2017. A 732-byte Python script gets root with no race conditions. Patch now." last_modified: "2026-05-01T17:41:43+00:00" categories: [Notes] --- # CVE-2026-31431: Update Your Kernel Now CVE-2026-31431, named Copy Fail, is a logic flaw in the Linux kernel’s `authencesn` cryptographic template. It has been sitting in the kernel since 2017, and as of 29 April 2026, there is a working public exploit for it.If you run Linux, you are probably affected. ### What Copy Fail Actually Does Most privilege escalation bugs require winning a race condition or knowing kernel-specific memory offsets. As [Theori’s technical write-up](https://xint.io/blog/copy-fail-linux-distributions) explains, Copy Fail is a straight-line logic flaw that needs neither — the same 732-byte Python script roots every Linux distribution shipped since 2017, with no per-distro offsets and no recompilation. [The Register’s coverage](https://www.theregister.com/2026/04/30/linux_cryptographic_code_flaw/) notes the proof of concept is a 10-line Python script using only standard library modules. It edits a setuid binary’s cached copy in the page cache to gain root, without triggering any filesystem-level defenses like inotify. The technical chain: a bug in `authencesn` combined with `AF_ALG` and `splice()` produces a 4-byte controlled write into the page cache of any readable file. That is enough. The CVE is rated High severity at 7.8/10. ### Who Is at Risk Copy Fail requires only an unprivileged local user account — no network access, no kernel debugging features. The kernel crypto API (`AF_ALG`) ships enabled in essentially every mainstream distro’s default config, so the entire 2017-to-patch window is exposed out of the box. The highest-risk systems are multi-tenant ones. Per the [official advisory at copy.fail](https://copy.fail/): shared dev boxes, jump hosts, build servers, Kubernetes nodes where a pod can compromise the host and cross tenant boundaries, and CI runners that execute untrusted PR code. If you run GitHub Actions self-hosted runners or GitLab runners on shared infrastructure, a malicious pull request can become root on the runner host. [CERT-EU’s advisory](https://cert.europa.eu/publications/security-advisories/2026-005/) specifically recommends prioritising Kubernetes nodes and CI/CD runners for patching. Standard single-tenant servers are lower risk — the bug does not grant remote access on its own — but it becomes a trivial step-up if an attacker already has a foothold via web RCE or stolen credentials. ### How to Fix It **Patch first.** Update your distribution’s kernel package to one that includes mainline commit `a664bf3d603d`, which reverts the 2017 `algif_aead` in-place optimisation. Most major distributions are shipping fixes now — check your distro’s security tracker. If you cannot patch immediately, the common workaround is to disable the `algif_aead` module: BashCopy echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf rmmod algif_aead Code language: Bash (bash)However, as [CloudLinux’s write-up warns](https://blog.cloudlinux.com/cve-2026-31431-copy-fail-mitigation-and-patches), this does not work on RHEL-family distributions including AlmaLinux, because `algif_aead` is built directly into the kernel rather than as a loadable module. The commands run without errors but leave the system unchanged, giving a false sense of protection. For containers regardless of patch status, block `AF_ALG` socket creation via seccomp — since the exploit requires opening an `AF_ALG` socket as its first step, this stops it cold even on an unpatched kernel. ### The AI Angle Worth flagging because it changes the threat model going forward: Theori researcher Taeyang Lee identified the vulnerability with the help of the company’s AI security scanner, [Xint Code](https://code.xint.io/). According to [Bugcrowd’s analysis](https://www.bugcrowd.com/blog/what-we-know-about-copy-fail-cve-2026-31431/), the system surfaced the bug in about an hour of scan time against the Linux `crypto/` subsystem, with one operator prompt and no harnessing. A nine-year-old kernel bug, in a subsystem that countless researchers have audited, found by an AI in an hour. The assumption that the supply of kernel-grade bugs is roughly bounded by how many humans are looking for them is no longer safe. --- --- title: "AI Didn’t Go Rogue, You Handed It the Keys" url: "https://journal.alsonkaw.com/ai-didnt-go-rogue-you-just-handed-it-the-keys/" lang: "en-US" type: "post" description: "A Cursor agent wiped a company's production database in 9 seconds. The AI didn't go rogue — the owner handed it root access and stored backups on the same volume." last_modified: "2026-04-29T16:18:04+00:00" categories: [Notes] --- # AI Didn’t Go Rogue, You Handed It the Keys A founder let Cursor — with Claude running under the hood — loose on their production environment with a blanket root access token. Nine seconds later, the database was gone. The backups, stored on the same volume, went with it.The headline writes itself as an AI horror story. The postmortem is much less dramatic. ### Not a Rogue Agent. A Blank Cheque. Community consensus was immediate: this wasn’t Claude going rogue, it was a textbook DevOps failure wearing an AI costume. Root access to production. Backups co-located with the data they’re meant to protect. No staging environment. No least-privilege principle anywhere in the chain. The agent just executed what it was given permission to execute, efficiently and without hesitation. Railway, the cloud provider, caught some collateral flak in the thread. Their founder showed up to clarify: the user explicitly chose a blanket access token, and the backups were actually recoverable through Railway’s own infrastructure. As [Tom’s Hardware reported](https://www.tomshardware.com/tech-industry/artificial-intelligence/claude-powered-ai-coding-agent-deletes-entire-company-database-in-9-seconds-backups-zapped-after-cursor-tool-powered-by-anthropics-claude-goes-rogue), the data loss was self-inflicted, not architectural. ### The Vibecoding Tax The “vibecoding CEO” framing stuck because it fits. This is what happens when someone with genuine product instincts but no infrastructure background treats AI agents like autonomous contractors rather than very fast, very literal tools with no concept of consequences. Claude will happily `DROP TABLE` in production if you hand it a connection string and ask it to clean things up. It has no understanding of irreversibility unless you build that constraint in through permissions, environment separation, and tooling that forces confirmation before anything destructive runs. ### The Hygiene Hasn’t Changed None of this is an argument against AI-assisted development. It’s an argument for the same practices that have been required since databases existed: separate credentials per environment, off-volume backups, and the principle that anything capable of writing to production should earn that access one operation at a time. The agent didn’t take anyone’s job here. It did exactly what an unsupervised junior with root access and no checklist would have done, just faster. --- --- title: "Style Guide" url: "https://journal.alsonkaw.com/style-guide/" lang: "en-US" type: "page" description: "Style Guide This is a style guide page. Images Headings Heading 1 — Display type Heading 2 — Section header Heading 3 — Subsection Heading 4 — Minor header Paragraph This is body text. It should be readable, calm, and" last_modified: "2026-04-22T08:04:13+00:00" custom_fields: exclude_local_search: 1 --- # Style Guide # Style GuideThis is a style guide page. ## **Images** ![](https://journal.alsonkaw.com/wp-content/uploads/2026/04/240920-setup-c-scaled-1-1024x656.webp)![](https://journal.alsonkaw.com/wp-content/uploads/2026/04/wall-1024x640.jpg)**Headings** # **Heading 1 — Display type** ## **Heading 2 — Section header** ### **Heading 3 — Subsection** #### **Heading 4 — Minor header** ### **Paragraph** This is body text. It should be readable, calm, and consistent in both light and dark mode. Here’s a second line to check rhythm and spacing. ### **Inline links** A normal inline link looks like this: [**Curabitur blandit**](#). An external inline link looks like this: [**Go to Google**](https://google.com). ### **Lists** - Unordered list item - Another item Nested item - Nested item - Ordered list item - Another item Nested item - **Nested item** ### **Divider** --- ### **Quote (Block Quote)** > Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. **Curabitur** blandit tempus porttitor.   ### **Pull Quote (Pullquote block)** > Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor. —Pull quote cite ### **Code (inline)** Use docker compose exec for quick checks. BashCopy docker compose exec app php -v docker compose exec app php occ -V Code language: Bash (bash)PHPCopy fn.apply( this, args ), delay ); }; } const handleSearch = debounce( async ( query ) => { if ( query.length < 3 ) return; const res = await fetch( `/api/search?q=${encodeURIComponent( query )}` ); const data = await res.json(); console.log( `Found ${data.results.length} results` ); }, 400 ); Code language: JavaScript (javascript)PythonCopy import json from pathlib import Path from datetime import datetime def load_config(filepath: str) -> dict: """Load and validate a JSON config file.""" path = Path(filepath) if not path.exists(): raise FileNotFoundError(f"Config not found: {filepath}") with path.open("r", encoding="utf-8") as f: config = json.load(f) config.setdefault("version", "1.0") config["loaded_at"] = datetime.utcnow().isoformat() return config if __name__ == "__main__": cfg = load_config("settings.json") print(f"Loaded config v{cfg['version']}") Code language: Python (python) ### **Table** | Column | Value | | --- | --- | | Font | Inter | | Accent | 3896C6 | --- --- title: "What if your password manager stored nothing" url: "https://journal.alsonkaw.com/what-if-your-password-manager-stored-nothing/" lang: "en-US" type: "post" description: "HIPPO is a proposed storeless password manager — no vault, no stored passwords, just cryptographic computation on demand. Interesting tradeoffs." last_modified: "2026-04-20T10:43:14+00:00" categories: [Notes] --- # What if your password manager stored nothing Interesting concept [from Hackaday.](https://hackaday.com/2026/04/15/dont-trust-password-managers-hippo-may-be-the-answer/) HIPPO is a proposed storeless password manager — instead of encrypting and storing your passwords in a vault, it computes them on the fly every time using an [oblivious pseudorandom function (OPRF)](https://en.wikipedia.org/wiki/Oblivious_pseudorandom_function).Two secrets go in: your master passphrase on the client side, and a secret key held by the [HIPPO server](https://spectrum.ieee.org/storeless-password-manager). Neither is ever transmitted raw. The output is a unique, deterministic, high-entropy password for each site — generated fresh on demand, never stored anywhere. ## The tradeoffs The upside is real: no vault means nothing to crack. The tradeoff is equally real: you’ve just moved the single point of failure from your vault to the HIPPO server and its secret key. Vault compromise vs. server compromise — different attack surface, similar consequence if it goes wrong. One commenter put it bluntly: “So, we’re back to one password for everything?” — which isn’t quite accurate, but the concern behind it is. Your master passphrase still carries enormous weight. If it’s weak or exposed, the server secret becomes your only line of defence. The other gap: no native 2FA support. You’d need to layer that separately, which means HIPPO alone isn’t a complete solution. Not a live service yet, so this is still theoretical. But the approach is worth watching — especially for anyone already sceptical of trusting a third-party vault after the [LastPass breach](https://www.bleepingcomputer.com/news/security/lastpass-hackers-stole-customer-vault-data-in-cloud-storage-breach/), where stolen encrypted vaults were [still being cracked years later](https://thehackernews.com/2025/12/lastpass-2022-breach-led-to-years-long.html). Worth noting: one commenter suggested a mental algorithm instead — derive passwords from a formula in your head using site names and a memorable phrase. Neat in theory, but the moment someone figures out your algorithm, every password you’ve ever made is compromised. At least with HIPPO the server secret is an additional unknown. --- --- title: "WebinarTV turned your private meeting into content" url: "https://journal.alsonkaw.com/webinartv-turned-your-private-meeting-into-content/" lang: "en-US" type: "post" description: "A company has been crawling Zoom for meeting links, sending fake attendees in to record, then turning it all into AI podcasts. Full writeup on the main blog." last_modified: "2026-04-18T10:41:16+00:00" categories: [Notes] --- # WebinarTV turned your private meeting into content A company called WebinarTV has been crawling the web for Zoom links, sending bots with fake identities into meetings, recording everything without consent, and converting it all into AI-generated podcasts.The kicker: they notify you after. Not before. Wrote about it in full over on the main blog — the mechanics, the people affected, the CEO’s history of doing exactly this with other platforms, and what you can actually do about it. [Your meeting. Their podcast.](https://alsonkaw.com/muse/your-meeting-their-podcast/) --- --- title: "Claude feels dumber lately — here’s the fix" url: "https://journal.alsonkaw.com/claude-feels-dumber-lately-heres-the-fix/" lang: "en-US" type: "post" description: "Claude's chat responses got noticeably worse — turns out it's a default effort setting change. Custom instructions fixed it." last_modified: "2026-04-16T16:15:38+00:00" categories: [Notes] --- # Claude feels dumber lately — here’s the fix If you use Claude through the chat interface (not the API or Claude Code), you might have noticed responses getting shorter, shallower, and more bullet-pointy over the last month. Turns out it’s not your imagination. ## What happened Anthropic appears to have changed the default “reasoning effort” setting. Claude Code users can override this with /effort max, but the regular chat interface has no equivalent toggle. No announcement, no setting, no opt-out — just noticeably worse responses. The model isn’t broken. It’s being told to think less by default. ## The workaround Go to Settings → General → “What personal preferences should Claude consider in responses?” and add something like: > Always reason thoroughly and deeply. Treat every request as complex unless I explicitly say otherwise. Never optimize for brevity at the expense of quality. Think step-by-step, consider tradeoffs, and provide comprehensive analysis. Claude can’t control its own effort level, but it does respond to strong signals in the system prompt. Custom instructions act as that signal on every message. ## Does it actually work? Yes. The difference is immediately noticeable — fuller context reading, actual tradeoff analysis, and real depth instead of surface-level summaries. I’ve been running this for a few weeks and it’s been consistently better. The irony: Claude itself suggested this workaround when asked why its responses felt off. --- --- title: "portless | Named .localhost URLs for Development" url: "https://journal.alsonkaw.com/portless-named-localhost-urls-for-development/" lang: "en-US" type: "post" description: "Portless replaces localhost ports with stable name.localhost URLs, adds optional HTTPS, supports subdomains and git worktrees, and auto-assigns ports." last_modified: "2026-04-15T04:40:35+00:00" categories: [Link, Notes] --- # portless | Named .localhost URLs for Development [https://port1355.dev](https://port1355.dev) --- --- title: "Security hardening for WordPress" url: "https://journal.alsonkaw.com/security-hardening-for-wordpress/" lang: "en-US" type: "post" description: "A practical walkthrough of how I hardened my WordPress setup with passkeys / 2FA, Cloudflare Access, security headers, CSP tuning, and user enum fixes." last_modified: "2026-04-18T09:14:22+00:00" categories: [Notes] --- # Security hardening for WordPress This is a running log of the security hardening I’ve applied to this WordPress install. The goal: reduce attack surface, keep admin access tight, and keep the site fast + stable. - Strong authentication Enabled 2FA for admin access. ([Two Factor](https://wordpress.org/plugins/two-factor/) by WordPress) - Enabled passkey/WebAuthn login for stronger phishing-resistant auth. ([WebAuthn Provider for Two Factor](https://wordpress.org/plugins/two-factor-provider-webauthn/) by Volodymyr Kolesnykov) - Disabled WordPress comments entirely (no comment endpoints or spam surface). - Cloudflare edge protection (Tunnel + Access) Site is served through Cloudflare Tunnel (origin not directly exposed). - Restricted backend access using Cloudflare Access: /wp-admin* and /wp-login.php* require authentication. - Public site stays public. - WordPress attack surface reduction Blocked common user enumeration vectors: /?author=1 style enumeration blocked. - Author archives redirected away (no public author pages). - REST user endpoints blocked for anonymous users (prevents username scraping) while keeping Elementor/editor working for logged-in users. - XML-RPC disabled (and optionally blocked at the edge): - Removes an old remote-auth surface and pingback abuse vector. - Security headers (Cloudflare Response Header Transform Rule) Set at the edge so they apply consistently and don’t depend on the container/webserver. - Strict-Transport-Security: max-age=31536000; includeSubDomains; preload - X-Content-Type-Options: nosniff - X-Frame-Options: SAMEORIGIN - Referrer-Policy: strict-origin-when-cross-origin - Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=() - Cross-Origin-Opener-Policy: same-origin - CSP (Content Security Policy) Started in Report-Only mode to avoid breaking Elementor, then tightened. - Current CSP allows: self-hosted assets - inline styles/scripts needed for Elementor - blob: workers (Elementor can spawn workers from blob URLs) - Plausible analytics endpoint/script - Example structure (high-level): default-src ‘self’ https: - script-src ‘self’ https: ‘unsafe-inline’ … - style-src ‘self’ https: ‘unsafe-inline’ - worker-src ‘self’ blob: - connect-src ‘self’ https: … (includes Plausible) - Privacy-first analytics Only analytics is self-hosted Plausible. - No ad networks, no third-party marketing pixels, no selling/sharing of visitor data. - Performance-related hardening Removed jQuery Migrate Reduced attack surface + less JS - Deferred non-critical custom scripts Theme toggle, code copy - Reduced unnecessary WP features Disabled wp-emoji, XML-RPC, commenting functionalities etc. ## Checklist for future me - Keep WP core/plugins/themes updated. - Periodically re-run security header + CSP checks (curl -I + DevTools). - Re-verify Cloudflare Access rules after any domain/routing changes. - Maintain backups + verify restoring works. --- --- title: "Extending Official Docker Images with Dockerfile" url: "https://journal.alsonkaw.com/extending-official-docker-images-with-dockerfile/" lang: "en-US" type: "post" description: "Learn how to extend official Docker images with a Dockerfile—example adds FFmpeg to Nextcloud for video thumbnails and previews." last_modified: "2026-04-22T06:40:09+00:00" categories: [Notes] --- # Extending Official Docker Images with Dockerfile Sometimes, packages you want are not built into the images developers prepare for you. You don’t need to rebuild everything from scratch. Most of the time, you can extend an official image, install what you need, and run the upstream entrypoint as-is. **Example: Add FFmpeg to Nextcloud** Nextcloud can generate better previews (especially for videos) when ffmpeg is available inside the container. ## 1. Create a Dockerfile Create a Dockerfile next to your docker-compose.yml: DockerfileCopy FROM nextcloud:apache # Install ffmpeg (Debian-based Nextcloud image) RUN apt-get update \ && apt-get install -y --no-install-recommends ffmpeg \ && rm -rf /var/lib/apt/lists/* Code language: Dockerfile (dockerfile) Notes: - FROM nextcloud:apache keeps the official base. - –no-install-recommends reduces bloat. - rm -rf /var/lib/apt/lists/* keeps layers smaller. ## 2. Use it in docker-compose Point your Nextcloud service to the Dockerfile: DockerfileCopy services: app: build: . image: my-nextcloud-with-ffmpeg:latest ports: - "8080:80" volumes: - nextcloud_html:/var/www/html depends_on: - db - redis Code language: Dockerfile (dockerfile) ## 3. Build + verify BashCopy docker compose build app docker compose up -d docker compose exec app ffmpeg -version Code language: Bash (bash) If you see the version output, it’s installed. ## 4. (Optional) Confirm Nextcloud is using previews In Nextcloud, enable or confirm preview settings as needed, then trigger video thumbnail generation by browsing a folder containing videos (or running your preview generator job). --- --- title: "About" url: "https://journal.alsonkaw.com/about/" lang: "en-US" type: "page" description: "About This journal micro-blog is my working log: short notes on what I’m learning, building, and debugging. It’s meant to be searchable, linkable, and useful to future-me — a brain log of things I don’t want to forget.This is where" last_modified: "2026-04-21T15:38:15+00:00" --- # About # AboutThis journal micro-blog is my working log: short notes on what I’m learning, building, and debugging. It’s meant to be searchable, linkable, and useful to future-me — a brain log of things I don’t want to forget. This is where the messy, in-progress thinking lives. If something grows into a proper guide or essay, it graduates to my [main site](https://alsonkaw.com). ## What you’ll find here **Notes** — short lessons, reminders, and observations. **Links** — “bookmark + takeaway” so I can find the good stuff later. **Snippets** — small code fragments with context. **Devlogs** — progress notes, decisions, and next steps for projects. Links will have an arrow-out icon beside it to imply it’s linking out to an external link. ## Topic System All my notes are organized with two taxonomies: **Domain** (e.g. Docker, WordPress, Linux, …)**Topic** (e.g. Performance, DNS, Caching, …) You’ll see them displayed as: Domain//Topic when you click into the notes. This is to facilitate easy filtering and searching. ## What this isn’t This isn’t a polished blog. It’s closer to a notebook. It’s designed to be minimal, fast, and updated whenever there’s something worth keeping. ## Where the long form posts go If you’re looking for more complete writeups, tutorials, or case studies, those live on my main blog: [alsonkaw.com](https://alsonkaw.com) --- --- title: "Now" url: "https://journal.alsonkaw.com/now/" lang: "en-US" type: "page" description: "Now This page is a snapshot of what I’m focused on right now—updated occasionally when priorities shift. Current Focus Building and optimizing Ash+Zelkova — a digital agency based in Singapore focused on web and graphics.Improving my systems and workflows: shipping" last_modified: "2026-04-21T04:49:59+00:00" --- # Now # NowThis page is a snapshot of what I’m focused on right now—updated occasionally when priorities shift. ## Current Focus - Building and optimizing **Ash+Zelkova** — a digital agency based in Singapore focused on web and graphics. - Improving my systems and workflows: shipping faster, staying lean, and keeping quality high. - Documenting what I learn along the way (tools, process, performance) in short, searchable notes. - Endlessly tinkering on my home-lab, self-hosting all kinds of applications, including this micro-blog. ## Current Projects **Ash+Zelkova **Digital agency work across websites, design systems, and brand/visual production. What I’m doing right now: - polishing delivery workflows and reusable components - performance-first builds (clean UX, fast load, minimal bloat) - tightening process around handoff, maintenance, and iteration Website: ashzelkova.com**Journal**** This micro-blog: a working log of what I’m learning, building, and debugging—kept minimal, fast, and easy to browse.Project Apollo**** Experimental personal curated music player & playlist. Website: [apollo.alsonkaw.com](https://apollo.maya329.com/) Morsels & Memories **A food + travel blog (coming soon). Building the foundation and structure first, then publishing consistently. ## Previously **Infinity Lootbox** (2024) eCommerce on TikTok store selling blind-box toys. Portfolio: [ashzelkova.com](https://ashzelkova.com/portfolio/infinity-lootbox/) **The Prinsep Crew** (2019–2020) A podcast project from 2019–2020. Listen: [spotify.com](https://open.spotify.com/show/5nxLZD2MnHdYxZRtyGPBGQ) **BeeryTale** (2017–2019) A pub on Prinsep Street in Singapore—closed due to COVID-19. Portfolio: [ashzelkova.com](https://ashzelkova.com/portfolio/beerytale/) --- --- title: "Topics" url: "https://journal.alsonkaw.com/topics/" lang: "en-US" type: "page" description: "Browse posts by domain and topic Image AI Security Docker Authentication Privacy Prompting Localhost Commit Passkey Security Performance DevTools Git WordPress Nextcloud" last_modified: "2026-04-21T15:28:59+00:00" --- # Topics # Browse posts by domain and topic - [Image](https://journal.alsonkaw.com/topic/image/) - [AI](https://journal.alsonkaw.com/domain/ai/) - [Security](https://journal.alsonkaw.com/domain/security/) - [Docker](https://journal.alsonkaw.com/domain/docker/) - [Authentication](https://journal.alsonkaw.com/topic/authentication/) - [Privacy](https://journal.alsonkaw.com/topic/privacy/) - [Prompting](https://journal.alsonkaw.com/topic/prompting/) - [Localhost](https://journal.alsonkaw.com/topic/localhost/) - [Commit](https://journal.alsonkaw.com/topic/commit/) - [Passkey](https://journal.alsonkaw.com/topic/passkey/) - [Security](https://journal.alsonkaw.com/topic/security/) - [Performance](https://journal.alsonkaw.com/topic/performance/) - [DevTools](https://journal.alsonkaw.com/domain/devtools/) - [Git](https://journal.alsonkaw.com/domain/git/) - [WordPress](https://journal.alsonkaw.com/domain/wordpress/) - [Nextcloud](https://journal.alsonkaw.com/domain/nextcloud/) --- --- title: "How Passkeys Work" url: "https://journal.alsonkaw.com/how-passkeys-work/" lang: "en-US" type: "post" description: "A quick takeaway on how passkeys work—what they are, why they’re safer than passwords, and what to know when adopting them." last_modified: "2026-04-15T04:40:57+00:00" categories: [Link, Notes] --- # How Passkeys Work [https://cassidoo.co/post/passkeys/](https://cassidoo.co/post/passkeys/) --- --- title: "Home" url: "https://journal.alsonkaw.com/" lang: "en-US" type: "page" description: "Hello there This journal micro-blog is my working log: short notes on what I’m learning, building, and debugging. Why a micro-blog? Latest Notes" last_modified: "2026-04-21T03:29:41+00:00" --- # Home # Hello thereThis journal micro-blog is my working log: short notes on what I’m learning, building, and debugging. [Why a micro-blog?](/about/) ## Latest ## Notes --- --- title: "Privacy Policy" url: "https://journal.alsonkaw.com/privacy-policy/" lang: "en-US" type: "page" description: "Privacy Policy Last updated: March 10, 2026This website (“the site”) is a personal journal and blog. I respect your privacy and keep data collection to a minimum. What information I collect Analytics (Plausible) I use Plausible Analytics, a privacy-focused analytics" last_modified: "2026-04-22T04:55:31+00:00" --- # Privacy Policy # Privacy Policy Last updated: March 10, 2026 This website (“the site”) is a personal journal and blog. I respect your privacy and keep data collection to a minimum. ## What information I collect ## Analytics (Plausible) I use Plausible Analytics, a privacy-focused analytics tool, to understand general traffic and improve the site (e.g., which pages are read most). - Plausible is self-hosted on my own server. - Analytics data is used for site performance and content insights only and never leave the server. - I do not use analytics data to identify you personally.   Plausible is designed to avoid tracking individuals. It does not use cross-site tracking and does not build detailed profiles of visitors. (For details, you can refer to Plausible’s documentation.) ## Server logs Like most websites, the server may temporarily log basic technical information for security and reliability (e.g., IP address, user agent, request path, timestamps). These logs are used to maintain the site, detect abuse, and troubleshoot issues. Logs are not used for advertising or profiling. ## Cookies This site does not use tracking cookies for analytics. If WordPress sets functional cookies (for example, if you log in as an admin), those are required for the site to function properly. For normal visitors, the site does not require cookies to browse. ## How I use information I use collected information to: - understand aggregate site usage (analytics) - maintain and secure the site - troubleshoot performance and errors ## Sharing of information I do not sell your personal information. I do not share analytics or visitor data with advertisers or data brokers. Analytics is self-hosted and not sent to third-party analytics providers. ## Data retention - Analytics data is retained for as long as needed for trend analysis and site improvement. - Server logs are retained only as long as necessary for security and maintenance. ## Your choices You can use browser settings or extensions to limit or block analytics scripts. The site should remain readable even if analytics is blocked. ## Contact If you have questions about this policy, you can contact me at: hello [at] alsonkaw [dot] com --- --- title: "Nextcloud felt sluggish, MariaDB was the bottleneck" url: "https://journal.alsonkaw.com/nextcloud-felt-sluggish-mariadb-was-the-bottleneck-not-php-redis/" lang: "en-US" type: "post" description: "Nextcloud lagged because MariaDB was maxed—InnoDB tuning + repairs + throttling fixed it." last_modified: "2026-04-18T09:17:36+00:00" categories: [Notes] --- # Nextcloud felt sluggish, MariaDB was the bottleneck My personal Nextcloud in Docker started feeling slow after updating to 33.0.0 (especially image browsing) even though the app container wasn’t busy. docker stats showed the DB container pegged while previews/thumbnails were generating. What was happening: - lots of churn in oc_filecache (tons of DELETE … WHERE path_hash=…) - DB CPU stayed high while UI felt laggy - Nextcloud would hang and load endlessly after browsing a folder with lots of images or videos without thumbnails. The Fix: - **InnoDB buffer pool**: bumped to 16GB so filecache queries stop hitting disk constantly. - **Redo log sizing**: increased redo logs (e.g. innodb_log_file_size = 2G) to handle heavy write bursts more smoothly. - **Nextcloud DB recommendations**: ensured DB is running READ-COMMITTED + binlog_format = ROW. - **Indexes/repair**: ran Nextcloud’s DB maintenance commands (missing indices / repair) to reduce expensive scans. - **Throttled preview workload**: reduced preview/imaginary concurrency so DB wasn’t being hammered by too many parallel preview jobs. Lesson: when Nextcloud feels “slow”, check DB CPU + filecache churn first. Redis can be working perfectly and you’ll still feel lag if MariaDB is the bottleneck. --- --- title: "How to Write Better Git Commit Messages" url: "https://journal.alsonkaw.com/how-to-write-better-git-commit-messages/" lang: "en-US" type: "post" description: "A concise guide to writing better Git commit messages—clear subjects, useful context, and patterns that make history easier to read and review." last_modified: "2026-04-15T04:41:06+00:00" categories: [Link, Notes] --- # How to Write Better Git Commit Messages [https://www.freecodecamp.org/news/how-to-write-better-git-commit-messages](https://www.freecodecamp.org/news/how-to-write-better-git-commit-messages) --- --- title: "Migrating a Docker image to an air-gapped VM" url: "https://journal.alsonkaw.com/migrating-a-docker-image-to-an-air-gapped-vm/" lang: "en-US" type: "post" description: "Step-by-step guide to migrating Docker images to an air-gapped VM using docker save/load, plus tips for tagging, updates, and rollback." last_modified: "2026-04-18T09:27:31+00:00" categories: [Notes] --- # Migrating a Docker image to an air-gapped VM One of my projects needs a web-facing VM that’s totally locked down: no apt-get, no outbound pulls from ghcr.io/docker.io. So the workflow is: build the image somewhere else, then ship it to the VM for updates. ## The simple path: docker save → copy → docker load On the build machine: BashCopy # build or pull normally docker build -t myapp:2026-03-04 . # export to a tarball docker save -o myapp-2026-03-04.tar myapp:2026-03-04 Code language: Bash (bash) Copy to the VM (scp/rsync works even if the VM can’t pull images): BashCopy scp myapp-2026-03-04.tar admin@vm:/tmp/ Code language: Bash (bash) On the locked-down VM: BashCopy docker load -i myapp-2026-03-04.tar Code language: Bash (bash) > IMPORTANT: BACK UP & DOUBLE CHECK PERSISTENT PATHS BEFORE NEXT STEP So… I did this, without backing up and without checking for persistent paths in the Dockerfile and my entire installation got overwritten… So here’s a reminder to do that before doing any of these. Then update your compose to reference the new tag: BashCopy # Example: in docker-compose.yml set image: myapp:2026-03-04 docker compose up -d Code language: Bash (bash) ## A small improvement: keep tags + rollback easy I tag images with a date (or git SHA) and never “overwrite latest” on the VM. That way rollback is instant: BashCopy # rollback docker compose down # change tag back in compose docker compose up -d Code language: Bash (bash) ## Note: If you’re shipping multi-arch If your build host and VM are different architectures, build the right arch (or multi-arch) before you export. Otherwise docker load will succeed but the container won’t run. ---