Linux

Passing the Sandbox Doesn't Mean It Works — 7 False Signals
The automated ordering system I wrote passed every command in a provider's sandbox, but almost everything the sandbox told me differed from reality, from response times and charges to accepted names and items that were created successfully but could not be found. Here are 7 false signals and how to write code that does not fall for them.

Silent Overwrites on Upload: Three File Copying Traps
Upload two files with the same name together, and the second silently overwrites the first. No error, no failing exit code, and every page still returns 200. The English page just becomes a Thai page. These are three traps that let file copying cause damage without any warning.

Making 20,000+ API Calls That Resume Without Leaving Corrupt Files
Generating 26,850 files through an API limited to 20 calls per minute requires three pieces—a system-wide throttle, resuming from what is already on disk, and a separate file validator—because files interrupted by network failures still have the right extension and a nonzero size.

The Monitor That Hammered Devices Until the Whole Farm Went Down
A monitoring loop that runs every 30 seconds and tries to recover every device that fails a ping sounds useful, until one device hangs and gets hammered repeatedly until the whole bus goes down. This article walks through ruling out hardware, tracking what was accessing the devices, and the settings that fixed it.

Ingesting Mail Without Disrupting Someone Else's Mailbox
The mailbox the app fetches from is one people actually use every day — fetching with UNSEEN silently misses mail when the owner reads it on their phone before cron runs. The right approach is UID SEARCH paired with UIDVALIDITY and BODY.PEEK, with deduplication handled on our side.

Reads from a Replica Must Show How Many Minutes Old the Data Is
replication can report a lag of 0 seconds while the data is stuck at yesterday, because these two signals measure different things — how to set up a monitor that checks both Seconds_Behind_Master and business data freshness, catches anyone writing directly to the slave, and has another check watching the monitor itself

The App Uses UTC, People Use Thai Time — 7 Hours Off, No Errors
Scheduled for three in the afternoon, published at ten at night. No errors, nothing wrong in the logs, because the app stores UTC while the person entering the time thinks in Thai time. This article traces the issue from an env setting that has no effect to the single line that writes Thai wall-clock time into a column everyone reads as UTC.

Moving JSON Time Series to Columns Without Changing the Dashboard
I stored entire device frames as JSON arrays at one row per second. After two weeks, the tables had grown to 183 MB at the endpoint and 625 MB on the server, with JSON_EXTRACT queries that could not use an index — here is how I moved the data into int columns and had the API rebuild a sparse array without changing a single line of the dashboard.

Web vs. Worker — mkdir 0700 Disables Inherited ACL Permissions
One app runs as two users, with php-fpm handling web requests and a separate account running the queue. Even with default ACLs fully configured, they still cannot write to each other's directories. The cause is mkdir with mode 0700, which sets a mask that wipes out the effective ACL permissions, plus a deletion trap that requires permissions on the directory, not the file.

The Service Is Running but Dead — systemd restart Cannot Help
A long network outage broke DNS. A daemon holding a session with an external API failed to log in 194 times, then hung halfway through the process. It never exited, so the system never restarted it — why I stopped checking whether a process was alive and started measuring data freshness instead.

php -l Passes, Web Pages Return 500 — A Compile Gate Before Commit
Templates broke twice in one month even though every php -l check passed, because php -l does not understand Blade syntax. This article covers the six-stage gate I put before commit — compiling the views and then linting the compiled files, plus a script that sweeps every GET route through the Kernel.

Always Claim the State Before Sending Anything Out
A "read the state, then send" guard looks safe until a worker gets killed after sending but before writing to the database, and a retry sends the same thing to the customer twice. This article covers the full set of duplicate prevention measures I use, from atomic claims to retry_after and failed() handlers that must never overwrite a successful state.

One Codebase, Two Roles: Hosted Web, Worker at Home
The whole system used to run on one machine behind a home internet connection. When that machine went down, the team could not use anything. I split it into two roles using the same codebase — the web runs on an always-on host, while heavy work stays on the home machine, which pulls jobs itself, with an atomic job-claim table and an 18-step cutover plan with a rollback path.

Too Many Alerts Mean No Alerts: Tuning a Bot People Will Read
In one day, 28 events became 56 messages, and 57 percent of all events were repeat alerts every 30 minutes. This article covers recipient tiers, merging overlapping cycles, state that survives restarts, and why you must not add keepalive to a chat app session.

A Single-Page PHP and SQLite Document Vault That Keeps Originals
A document photo storage app for government submissions, using photos taken on site with a phone. Each upload produces three files—the display image, the original, and saved crop settings—so adjustments can be made later without losing quality, with automatic migration from JSON to SQLite and web server restrictions on folders containing personal data.

Printing Dozens of Government Forms from HTML with Correct Margins
A government submission document system where users click Print in their browsers and every sheet must fit within its margins, from measuring the actual printable area in millimeters and repeating headers and footers without position fixed, to four image frame types for different documents and automated overflow checks with Playwright instead of inspecting every sheet.

News Stuck for Three Days Because the Feed Wasn't Sorted by Date
The news site's homepage showed the same day's stories for three days straight, even though scheduled jobs ran, translations finished, and every step reported success. The causes were an RSS feed sorted by relevance rather than date, string sorting of pubDate that actually sorted by weekday name, and requesting too few stories, leaving nothing after filtering before the rest even got a look.

Sending Automated Job Files to Telegram and the Bot API's 50 MB Limit
Set up a small local service that takes files from scripts and sends them to me on Telegram, with outbound delivery only, no polling, and no incoming commands, along with the actual Bot API limits to know before writing it, including 50 MB per file, images over 10 MB that must be sent as documents, and the Thai text trap in curl on Windows.

CDP Still Responds but Commands Fail After Chrome Updates Itself
I run several automation jobs on one machine that need a real browser, and ran into two problems at once — jobs fighting over tabs and getting each other's results, and Chrome overwriting its own files during an update while the process was still running, leaving the port responsive but Runtime.evaluate unusable. This article covers how I isolate jobs, detect the failure, and repair it automatically.

A 25-Hour Power Outage Left Scheduled Jobs Missing
The machine running my automated jobs lost power three times in three days, with the longest outage lasting 25 hours. When it came back, I found that jobs scheduled directly through Task Scheduler had skipped their runs entirely, while jobs managed by a dispatcher that checked the time itself had all caught up. This article covers the setting to enable, what to avoid during recovery, and the traps that led me to misread the status.

Automated Image Uploads Stopped for 4 Days: ssh Chosen by PATH
Write subprocess.run(["ssh", ...]) and leave the system to choose the binary from the caller's PATH. One day the caller changes, and the binary changes with it. File transfers fail silently for 4 days while reporting success every time. This article covers how I tracked it down, a helper that probes the actual binary before using it, and a completeness check before declaring the job done.

Tell a Broken Link from an ISP Outage Before Restarting Anything
A typical watchdog restarts NetworkManager when ping fails, which is wrong half the time because no internet access may mean the ISP is down, not that the adapter has failed. This article distinguishes three states using nmcli, then escalates from gentle recovery to PCI remove and rescan without rebooting the machine.

Every Layer Reported Success While the Output Was Garbage
In one day, four layers of the same automated pipeline failed together, but every layer reported success without a single error line. The person who caught it was a user who said the site was not updating. These are the rules I wrote afterward so that failing stages could speak up for themselves.

Auditing prod with real customer data without leaving a single row
The system owner asked me to test the entire system on the live server holding customer data without leaving even a single junk row behind. My approach was to bootstrap Laravel myself and call controllers directly inside a transaction that I rolled back, with a COUNT of every table before and after as evidence.

Generating Thai PDF Quotes with Chromium and the Pitfalls I Hit
Building a system to generate PDF quotations from HTML with Chromium for multiple companies in a group and multiple templates, from an Executable doesn't exist error caused by looking for the wrong headless shell, to A4 layout and pagination, sanitizing Thai filenames, and a token-based document numbering system where an accidental slash broke the path.

Moving Computation from PHP to the Browser: 12 Seconds to 12 ms
A single page ran tens of thousands of subqueries and took 12 seconds to load. I fixed it by precomputing a summary table, sending a single 107 KB dataset, and moving the formulas to the browser, bringing computation down to 12 milliseconds, with a parity test that requires JS results to match PHP on every row.

Automating Remote Windows Tasks with SSH + Task Scheduler
Move heavy workloads to a more powerful machine over SSH, then automate them with schtasks — with pitfalls that cost me real time, from landing in PowerShell 5 instead of bash over ssh, to backslashes getting swallowed when writing .bat files, to file transfers hanging until I switched to plink

Decoding a Bluetooth Label Printer Protocol from btsnoop
My Bluetooth label printer only had a mobile app and no way to connect it to my own system, so I enabled HCI snoop logging on my own phone, peeled away HCI/ACL/L2CAP/RFCOMM one layer at a time with a short Python parser, and compared the results with open-source libraries until I could read the commands and print directly from a script.

The Web User's crontab Isn't the One Running Your Jobs
Schedule changes saved through the web still leave jobs running at the old times because the script writes the web user's crontab, not the user’s crontab that cron actually reads. How to spot it in 3 commands, the sudoers drop-in I used, and how to design it so this cannot happen again.

Laravel Queues on Shared Hosting — Don't Share Redis with Cache
If you're enabling queues for Laravel on shared hosting and planning to separate the queue from cache with different REDIS_DB numbers, that won't help. maxmemory and eviction apply to the whole instance, not individual DBs. This article explains why I chose the database driver instead, with complete systemd units to replace Supervisor.

MariaDB on defaults — a 128M buffer pool on a 55 GB RAM server
A 16 core server with 55 GB RAM and an SSD, but MariaDB still had the default 128 MB buffer pool. The result was 50 million disk reads in 5 days. The full before-and-after settings, service name and config path traps on DirectAdmin/Debian, rollback steps, and another 500 MyISAM tables waiting for conversion.

Bulk Import Legacy .xls Files into Laravel with PhpSpreadsheet
Legacy .xls files with Thai headers encoded in tis-620 have unreadable column names, large files make PhpSpreadsheet exhaust memory and crash, and files users merge themselves contain duplicate rows — use column positions instead of names, add a read filter, and deduplicate with a natural key.

Charset Mismatch Silently Kills JOIN Indexes — an utf8mb4 Mirror
The old database uses tis620 and the new one uses utf8mb4. JOIN across databases, and every index immediately becomes decorative without a single error — how to prove it with EXPLAIN and build a mirror that converts tis620 to utf8mb4 during the dump, plus the sed trap that silently breaks tables.

One Query Hung for 54 Minutes, Causing Sitewide 504s via MyISAM Locks
A single 30 MB table caused 504s on every page across the site, even with 9.5 GB of RAM still free — I traced information_schema.PROCESSLIST to a single SELECT that had been stuck for 54 minutes on a MyISAM table, cascading into metadata locks across the server, with an immediate fix and ways to prevent it from happening again.

Recover MariaDB Redo Log Corruption with force_recovery and Reinit
MariaDB would not start after an unclean shutdown — a corrupted InnoDB redo log took the mysql.global_priv system table down with it. This is the recovery sequence I actually used, starting with innodb_force_recovery to make the data readable, then dumping each db before reinitializing the whole system, with one rule never to raise force_recovery higher than necessary.

Thai Data Migration Traps: VARCHAR, Idempotency, and OCR Vowels
Lessons from importing and migrating hundreds of thousands of Thai data rows — undersized VARCHAR columns silently truncating synthetic keys into collisions, idempotency requiring DELETE before INSERT to stop totals reaching 200%, collation treating su001 and SU001 as equal while storing both, and Thai vowels corrupted by OCR that need a normalization map before insert

Porting Excel Formulas to the Web: Floating-Point Traps
Porting engineering formulas from an Excel file to a web calculator produced differences around 1e-13 — not a math bug, but differences in how JS and Excel evaluate floating-point powers and multiplication order, with a method for reading formulas and values separately using openpyxl and building fixtures that actually catch regressions.

Android Dev on MIUI/HyperOS — adb Traps on Windows
adb is not broken, but Git Bash mangles /sdcard paths, PowerShell produces 0-byte screenshots, MIUI/HyperOS blocks the first installation, and here-strings swallow backslashes when generating PHP code — real traps I hit while developing an Android app on Windows, with commands that actually work

Inferring Cold Room Defrost Timing from Temperature Slopes
A cold room used to dehydrate glass noodles has no humidity sensors, but frost on the coil always flattens the temperature drop slope (dT/dt). This article uses the lumped-capacitance equation and time constant τ to detect frost and trigger demand defrost with working Python code, along with calculations for R404A COP and water removed per cycle.

Government ICT Computer Price Criteria + DIY Builds with Scripts
The December 2568 government ICT reference price criteria clearly define specifications for 20,000 baht office computers and 24,000 baht processing computers. In this article, I read the actual criteria item by item, then write a Python script to pull Advice prices and put together DIY builds to compare how much better the value is than buying prebuilt machines.

reCamera + Gimbal: RISC-V Cross-Compilation, CAN, and Node-RED
reCamera is an AI camera on a RISC-V chip mounted on two brushless axes, with rotation controlled over CAN bus at a resolution of 0.01 degrees. This article covers cross-compiling C for riscv64 musl, uploading to the device with scp, the CAN frame structure for motor control, reading back the 15-bit encoder, finding motor zero using current, a Node-RED dashboard + PID + auto-track, and STP files for 3D-printing the housing.

Read a Toledo RS-232 Scale over Bluetooth with PowerShell
Connect a TWT-01 digital scale with only an RS-232 port to a PC through a BT578 Bluetooth adapter, then read its weight in real time with PowerShell — from Bluetooth pairing and finding the COM port by MAC address to decoding 18-byte Toledo Continuous frames and the null-byte and scale auto-sleep pitfalls I encountered in practice.

Reading a DTSU666 3-Phase Meter with Raspberry Pi over Modbus RS485
Notes on connecting a DTSU666 Smart Power Sensor to a Raspberry Pi over RS485, reading 3-phase electrical measurements from multiple meters using Modbus RTU, decoding 32-bit floats correctly, and storing them in SQLite/MySQL before exporting through MQTT and a REST API, including a negative-value bug I ran into myself

~30 Seconds Turns Tiny: Flarum's Litedown Subscript Trap
Write "~30 seconds" in a Flarum post and 30 turns into a tiny subscript — because Flarum's Litedown interprets ~ as a subscript opener. Thai users who use ~ to mean "approximately" run into this regularly. This article explains the cause, workarounds, and database fixes for content stored as s9e XML rather than raw markdown.

Writing Scripts on Windows for Linux — Two File Cleanup Traps
Writing helper scripts on Windows and using scp to run them on Linux is routine work, but two things keep catching me out — d:\ paths embedded in scripts turn into strange filenames in /tmp that trigger the file monitor, and token/payload files get left behind on the server. This article lays out my rules for avoiding these mistakes.

Reading Modbus RTU over an RS485 Gateway Without Phantom Values
Connecting multiple Modbus RTU meters/sensors on an RS485 bus through a serial-to-Ethernet gateway can produce phantom values, incorrect readings, or devices that do not actually exist — because a transparent gateway uses RTU-over-TCP, which requires assembling frames and calculating CRC yourself, and validating every reply. This article covers the rules I learned from doing it.

Create .ppk Files for plink/pscp on Windows Without Opening puttygen
puttygen.exe on Windows is GUI-only and cannot batch-convert OpenSSH to .ppk (it returns unrecognised option) — this article writes PPK v3 files directly with Python, including the hard-to-find HMAC-SHA-256 MAC formula, ready to use with plink/pscp/WinSCP.

Generate PromptPay QR (EMVCo) in Pure Python — No External Libraries
Want to generate PromptPay payment QR codes in your own POS/web system without calling a bank API? In this article, I build the EMVCo TLV string + CRC16 myself in pure Python, producing a payload that matches the Thai QR specification and can be scanned for payment by any banking app, along with the pitfalls that stop apps from reading it.

USB in WSL2 with usbipd-win — Real Hardware for Linux on Windows
WSL2 cannot see USB devices because it is a VM without a USB controller — usbipd-win passes devices from Windows directly to the WSL kernel with 3 commands, list/bind/attach, plus lessons from actual use: attachments drop when unplugged, the CRLF trap, and USB modems taking over the default route

Per-user Redis on shared hosting—and the maxmemory bug that hung sites
Designing one Redis instance per user on shared hosting with a systemd template + cgroup — and lessons from a maxmemory bug where setting it above the cgroup limit caused intermittent WooCommerce hangs, plus the 80% rule and a guard script to prevent recurrence

Legacy Apps Break After Migration — MariaDB Strict Mode Errors
After moving a website from an old server to a new one, the registration form breaks even though the code has not changed — the cause is STRICT_TRANS_TABLES in the sql_mode of newer MariaDB versions, with fixes at 3 levels, from conf.d to per-app SET SESSION

Using Claude to Upgrade Proxmox 8 to 9 and Rebuild Windows VMs
A real case using VS Code + Claude to upgrade an entire Proxmox cluster from 8 to 9, then delete and rebuild all Windows VMs for VDI without ever SSHing into a host myself. I have laid out the commands and scripts Claude generated during the actual work as steps that a person or AI can read and run again.

qcow2 or ZFS — Why I Switched After 12 Years on the Same Server
The reasons behind my storage choices for a Proxmox server that has run for 12 years without a reinstall — why I chose qcow2 in 2014, why I only moved to ZFS in 2026, and what actually changed the calculus.

Moving DA to Debian 12, Part 2 — Switching Storage to a ZFS Mirror
Replace the three storage layers of mdadm RAID1 + ext4 + qcow2 with a single layer of ZFS mirror + raw zvol while the old VM stays online, by degrading RAID to a single disk.