All articles

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.

Cross-Model Audits of AI-Written Code — Process, Not Faith
After using AI to port a calculator from a 35-sheet engineering spreadsheet, how do I know it is correct? This article covers three independent audit rounds by an agent from another provider, the rules to set before starting, confidence levels that keep claims within the evidence, and the classes of errors AI kept making in every round.

Labels Printing Off-Center? Calibrate the Print Offset Once
After getting the label printer to fire its printhead, the next problem was that the text missed the sticker. I stopped guessing and printed rulers and corner markers to measure it, then clearly separated paper size, printable area, and offset into three distinct values.

Bank Open API QR Payments — From Sandbox to Status Lifecycle
A static PromptPay QR is easy to build yourself, but nothing tells you whether the customer has paid. This article walks through a bank's Open API sandbox, from requesting a token and issuing a dynamic QR to tracking REQUESTED/PAID/CANCELLED/VOIDED states and the requestDt trap that rejects requests even when the entire payload is correct.

Drawing SVG Electrical Panels You Can Measure on Paper
The electrical panel drawings I generated from HTML looked good, but the technician said the proportions were wrong. I rebuilt the whole sheet, anchored it to actual catalog dimensions, wrote the viewBox directly in millimeters, and added a scale bar so the technician could use a ruler to verify that the drawing in hand really was at 1:10.

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.

Migrating an Old .aspx Site to a JSON Catalog and Filtering Images
The owner of an old .aspx site asked me to rebuild it, but more than a decade of content was in a database I could no longer access. I collected the pages from the site itself to build a JSON catalog that preserved every legacy_id, then wrote a three-layer image filter to keep stray QR codes from becoming article covers.

Let Clients Pick Photos on a Web Page Without a Login
When the person choosing photos for a website is not comfortable with computers, adding registration and login means the work stops at the login screen. This article covers a photo picker with no authentication at all, but with a server endpoint that checks every value against an allowlist strict enough to leave nothing from arbitrary input, plus limits at every layer and a backup before every write.

Build APM on Windows: Apache + PHP 8.4 + MariaDB Series Index
A 7-part series on compiling Apache 2.4.69, PHP 8.4.27 and MariaDB 11.8.9 from source on Windows 11, so when PHP releases a security patch, I can change the version number in one file and rebuild within a day. This page covers the contents, overview, time, disk space and three-command shortcut.

Build APM on Windows, Part 7: Deploy, Run, and Apply Security Patches
The final part of the series on building Apache + PHP + MariaDB on Windows. Install into C:\APM, start and test it for the first time, open it to the LAN safely, walk through six real bugs (PidFile, MariaDB plugins, the DB port, the ASLR 500, paths with spaces and brackets), and cover the procedure for the next security release.

Build APM on Windows Part 6: PHP 8.4 with redis and imagick
Part 6 of the series on building Apache + PHP + MariaDB on Windows from source walks through 30-php.ps1 step by step, from php-sdk and the full configure command to copying DLLs and testing that JIT, redis and imagick actually work.

Build APM on Windows, Part 5: Compile ImageMagick 7 for PHP imagick
Part 5 of the Build APM on Windows series compiles ImageMagick 7.1.2-34 and around 40 delegate libraries from source with the official toolchain, including the channel mask patch from 64 to 32 that lets imagick build, and why HEIC cannot be written

Build APM on Windows, Part 4: Compile MariaDB 11.8 LTS
Part 4 of the Build APM on Windows series walks through compiling MariaDB 11.8.9 from source with CMake and Ninja, from preparing the tools and running the script to testing on port 3399 and explaining why collation must be pinned to utf8mb4_unicode_ci

Build APM on Windows, Part 3: Apache httpd 2.4.69 with CMake
Build APR, APR-util, httpd 2.4.69 and mod_fcgid with CMake and Ninja on Windows. Explain why each option is set, use dumpbin to find the DLLs needed beside httpd.exe, prevent links to debug DLLs, and test the config with httpd -t.

Build APM on Windows, Part 2: Fetch Source and Libs with vcpkg
Steps 1 and 2 of building Apache yourself on Windows. Read 00-fetch.ps1 and 10-deps-vcpkg.ps1 line by line, verify the sha256 of every source archive, then have vcpkg build OpenSSL 3.5 LTS and 8 other libs from source, with commands to check the results at every step.

Build APM on Windows, Part 1: Prepare Tools and Unpack the Build Kit
The first part of a series on building Apache + PHP 8.4 + MariaDB yourself on Windows. Install Visual Studio 2022 Build Tools with the required components, Git, CMake, Ninja and PowerShell 7, configure Defender, unpack the build kit into C:\apm-build, then examine the folders, versions.json and the lib.ps1 and build-all.ps1 scripts line by line.

Two Critical PHP RCE Flaws (php2shell): Patch Today
CVE-2026-109618 and CVE-2026-109619 are PHP RCE vulnerabilities with a CVSS score of 9.2 and public exploits already available. They affect every machine using PHP to handle web requests. The patched versions are 8.5.12, 8.4.27, 8.3.36, and 8.2.35. This note covers version checks, updates on Debian, RHEL, DirectAdmin, Docker, and Windows, and temporary settings while waiting for patches.

Scripting Government PDF Forms and Making Safer ID Copies
Government PDFs distributed as fillable forms often cannot actually handle Thai text. Subdivided fields let digits run over the borders, and tone marks collide with vowels above the line. This article covers listing field names first, filling the fields and flattening them into page content, drawing Thai text with a font that includes all the vowel marks, and making actual-size ID copies with purpose-specific watermarks.

Building a Graph API Page App with Three Required Endpoints
When a backend needs to read and reply to Page messages on its own, the only proper route is to build an app and pass the platform's review. This article covers three endpoints the platform requires but people forget until they fail review, the correct way to verify incoming request signatures, and the steps to exchange a short-lived token for a Page token stored outside the web root.

VM Sees the Dongle but Has No Internet Without a Windows ECM Driver
After passing a USB LTE dongle through to a Windows VM, the system sees the device but no network adapter appears. The cause is neither the SIM nor port passthrough, but the dongle exposing a CDC ECM interface that Windows has no built-in driver for, along with storage-mode pitfalls and dhclient overwriting the host's resolv.conf.

A Report Form for a Static Site with No Backend
A calculator site made entirely of static files has no server to handle forms, but users still need a way to report mistakes in the formulas. This article covers the one approach I chose—a single worker file that accepts POST requests on one route, verifies Turnstile on the server using the real client IP, accepts images pasted from the clipboard, and sends email from the network edge—along with three traps that cost me an entire night.

Reverse Engineering Spreadsheet Constants into Formulas
A sheet has a criteria table sitting beside the calculations, but no VLOOKUP connects them. People read the table and type values into cells themselves, and mistakes go unnoticed. This article covers separating formula cells from manually entered cells, then reconstructing a single formula that matches all 16 values in a multiplier column.

Speed Up Screen Recordings While Keeping the Original Clock
How to make a timelapse from a screen recording by speeding up idle sections more than active sections, using split, trim, and setpts, then joining them with concat. The main trap is that drawtext must come before setpts for the clock to follow the original timeline instead of the accelerated one.

A 26-Page Bilingual Site: Why .php Broke Every Link
The production page displayed PHP tags as raw text because the web server served .html as static files. Changing all 26 pages to .php broke links across the site. Then, after adding clean URLs, subpages bounced back to .php because the rewrite and 301 setup was only half done. This article includes the full .htaccess, eight cases for $base, and scripts that handle the whole site in one pass.

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.

A Cookie Banner That Loads GTM and GA4 Only After Consent
Most cookie banners look good but block nothing, because GTM and GA4 have already loaded from the first line of the page. This article covers a setup that loads the container only after acceptance, puts GA4 inside GTM to prevent double counting, hides AdSense slots initially, and checks that outgoing requests to those services stay at zero.

A Blank Page with No Errors, All Because I Named a Function top
After deployment, the page was blank, the logs had no errors, and node --check found nothing wrong. But the browser stopped the entire script before the first line ran because a function named top collided with window.top. Here are the names to avoid, the script loading order trap, and the Playwright check every project should have.

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.

Images Fail on Mobile in Pages Hidden with display:none
A site switches pages using display:none, but opening another page on mobile leaves blank black images, even though the files return 200 and both fetch and new Image succeed. The culprit is the IntersectionObserver behind loading=lazy not firing when the page changes from none to block, and simply setting loading=eager is still not enough.

Reading ID Cards from the Web with a Localhost Agent
Browsers cannot access smart card readers directly. A workable approach is to write a small agent that opens a port on localhost and let the web page call it. This article covers secure contexts, CORS preflight, port conflicts with other agents, PC/SC in SHARED mode, decoding TIS-620 manually, and the day antivirus flagged the program as a backdoor.

The NVR Kept Beeping, Then Fell Silent When the Disks Failed
The NVR's all-day beeping was not videoloss as everyone thought. It was ipconflict from cameras using DHCP with conflicting IP addresses. Someone got annoyed and muted the whole unit, so when both disks failed together, there was not a single beep. This article fixes the policy one event at a time through ISAPI.

Cannot Delete Faces from a Terminal: API Returns notSupport
The door's face recognition terminal advertises a command to delete faces from FDLib, but every variation I tried returned notSupport. People who had left could still scan their faces and get in. This article covers the dead ends, the approach that actually worked using UserInfo/Modify with the smallest possible body, and the two-way user registry sync that runs every night.

No kWh Meter? Estimate Daily Electricity Costs from Current
Notes on converting CT current readings into energy usage and daily time-of-use electricity costs, from the three-phase power formula and the sampling interval built into it, through skipping outages and separating peak from off-peak with epoch arithmetic, to a daily summary table that made the web page seven times faster.

Reading an Unknown Modbus Device: Verify Before Interpreting
My notes on tracking down an undocumented device on an RS485 bus, from scanning baud rates and addresses one combination at a time to proving which registers hold live values and which are dead, and the lesson that guessing a device's type from number patterns alone can lead to months of misinterpretation.

Writing to DirectAdmin Through Panel Forms When the API Only Reads
Notes on letting a back-office app create and edit hosting packages in DirectAdmin when the official CMD_API only provides read operations, requiring POST requests directly to control panel forms, with verification rules that prohibit regex and two layers of kill switches disabled by default.

Registrar and Billing NS Mismatch: Audit All Three Sides First
A debugging log for "Wrong response from the server" when changing nameservers in a billing system, which failed only for domains with leftover glue IP data, followed by a three-way audit of the registrar, billing system, and actual DNS zones before I felt confident changing anything.

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

Let AI Read Documents, but Let Code Do the Math
The controller requires manually entered values to fall between 0 and 100, but the path where AI reads values from documents and feeds them in only checks whether they are numeric. A single negative value inflated the result to 5.25 times what it should have been without an error — a lesson in treating AI output as untrusted input, just like values entered by people.

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.

Secrets Set to 600, but 0644 Logs Go Offsite Every Night
The .env is encrypted and permissions are locked down on every secret file, but customer data still leaks off the machine every night through something nobody classifies as confidential: logs that every account on the machine can read, bundled into backups uploaded to object storage. Here is how I stop it at the source by logging only result codes.

Egress Firewall Breaks Domain Checks — Tests as root Pass
The domain order page reported every name as unavailable, even names I made up. The cause was an outbound DROP policy with port 43 missing from the allowed port list after an operating system migration. The trap was that tests as root passed every time. Testing must use the account that actually runs the app.

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.

tinker Sends Mail but the Web Can't — proc_open Disabled in FPM
Sending mail from tinker works perfectly, but submitting the actual form sends nothing, and the mail log has no entries — because CLI and PHP-FPM are not the same PHP environment. Includes a working SMTP AUTH configuration for DirectAdmin hosting and the From rule you must get right.

Site Not Updating After Deploy — Clear All Five Cache Layers
The files are uploaded, but the site still shows the old version, even after Ctrl+F5 — because five cache layers sit between the files on disk and what people see, and each needs a different clearing method. This article covers the sequence I use regularly, along with the trap that leaves the site stuck on 500 after clearing the view cache.

route:cache Turns Every GET into 405 — The Sub-path Trap
Deployment finished, I ran optimize as the guides suggested, and every page returned 405 Method Not Allowed even though route:list confirmed the routes were correct — a route cache that breaks only on certain servers, how to isolate it in 30 seconds, and three other issues I found while deploying an app under a sub-path.

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.

One Tunnel Is the Only Way In—and a Single Point of Failure
The machine sits behind NAT with no ports open to the internet. Every way in runs through one cloudflared tunnel — web, SSH, and remote desktop. This article covers the four ingress routes I set up, how to read the signs when it goes down, and the trap that keeps the tunnel from starting at boot.

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.

Mailbox Compromised or Just Password Guessing? Prove It First
Telling a customer their account has been compromised is an allegation that needs decisive evidence first. This article covers what counts as decisive evidence, two cases with opposite outcomes, and the case I nearly reported incorrectly.

Food Factory Drawings: Sheets, Scales, and Paper Sizes
A food factory application drawing set has 7 items, each with its own scale range, and some numbers to work out before drawing—how wide a plan will fit on A4 or A3 at 1 to 100, and how to split it across sheets without changing the scale if it will not fit.

From Model to 6-Meter Steel Cutting Layout and Workshop Cut Sheet
Read part lengths from the model, consolidate duplicates, then arrange a first-fit decreasing cutting layout on standard 6-meter stock with a 3-millimeter saw kerf allowance, plus a notch-and-bend option for right-angle corners that reduces joints from 12 to 6

Quantity Takeoff from SketchUp Models with Ruby Scripts
A finished model already contains every number a quantity takeoff needs. Give groups readable names from the start, then write a script to grep through them, with two traps to watch for that can double your areas.

SketchUp Drawing Sheets with Automatic Labels and Dimensions
A finished model is not ready for submission yet. What the authorities accept is a set of paper sheets with building numbers, dimension lines, a north arrow, and a scale. This article covers extracting geometry from the model as numbers, rendering a top ortho view as black lines on white, and letting a script place numbers and dimension lines without overlaps, with overrides for individual items.

Mapping a Building by Phone and Finding True North with GNSS
ARCore gives room-corner coordinates in meters accurately enough to use, but its world frame has an unknown orientation, so the resulting plan is always rotated by some angle. The fix is to collect pairs of (AR position, GNSS reading) while working, then fit a rotation using weighted 2D Kabsch, with weights based on each fix's accuracy, to find both north and the origin's coordinates.

Rebuilding an Off-Scale PDF Plan at Real Dimensions
Presentation plans are often drawn block by block, with different scales for the x and y axes and different scales again for each room. Calibrating the whole page once and tracing it gets everything wrong. This article covers extracting coordinates from paper into metres by recalculating the scale for every room, counting objects with colour-blob BFS instead of by eye, and drawing them back into a model at their real dimensions.

Shared Neutrals Across Transformers: Why Phase Balancing Is Not Enough
A case involving two transformers with interconnected neutrals and houses tapped into one side's main feeder, leaving phase A 30-40 A higher even on days off, plus a meter-reading trap that can suggest a 200 A difference if the CT ratio is not verified first, and a repair sequence that must be followed in order.

Sizing Main Cables and Breakers by Installation Method
Notes on choosing the main cable and breaker for a new 253 A feeder, why the same size CV cable can carry 227 A or 364 A depending on how it is installed, and the option of downsizing to 95 and compensating with operating rules, at the cost of the main breaker tripping if anyone breaks those rules.

Diagnosing Feeder Overload from Melted Insulation and Duty Cycles
My investigation of a 95 mm² main cable with insulation melting at scattered points even though the system seemed to work normally, from the nameplates of 6 washers and dryers totaling 234.7 kW to a minute-by-minute current model explaining why a 482 A load on a cable rated for 180 A had not caused a visible system failure.

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.

Borrowing a VLAN for Windows Tools That Need the Same L2
Many manufacturers' device discovery tools use broadcasts that cannot cross L3, so connecting over a VPN still leaves devices invisible. A solution that leaves switch ports alone is to put a small Windows VM on Proxmox with its interface directly in that VLAN, with pitfalls around vlan-aware bridges, a full thin pool breaking other VMs, and virtio leaving Windows without a network adapter after installation.

Managing Site Switches Remotely with UniFi's Cloud Connector Proxy
My workstation is in a different location from the site, with no VPN and no inbound ports open, but I still need to change VLANs per port, reserve fixed IPs, and track down failing cables. This article explains how Site Manager's connector proxy reaches the legacy API for both reads and writes without requesting a CSRF token, and the discipline needed when holding a key this powerful.

Set VLANs on a Hikvision Switch Using Its Web UI API
A smart managed switch whose Switch ISAPI endpoints return 403 can still be scripted if you inspect what its web UI sends. This article turns that API into a VLAN configuration script with a dry-run before apply, covering the traps I actually hit, including different field names for reads and writes, CCTV mode forcing 10 Mbps, and a calculated PoE load that exceeds the switch's budget.

Using an NVR to Reach Inaccessible Cameras with ISAPI and SDK
The cameras sit behind the NVR's second interface, and none are directly reachable. This article works through what the NVR's ISAPI can actually do, with 26 of 38 tested endpoints working, retrieves CCTV snapshots and RTSP through the NVR, finds when each channel last recorded, and falls back to HCNetSDK through ctypes to read device logs when ISAPI runs out of options.

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.

Static on DirectAdmin: GET Returns 404 but HEAD Returns 200
I uploaded a static site to public_html and could not open it. Small files worked, large files returned 404, but HEAD returned 200 for every file. The cause was Laravel's .htaccess at the docroot, funneling everything into the front controller. This article covers the diagnosis and three ways out.

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.

Every File Is Backed Up, but I Still Can't Rebuild the Server
The database dump is complete, the code tar is complete, and all the secrets are there, but put them on a blank machine and the site will not come up, because the things that actually make the server work were never in the backup files. This article lists what disappears with the machine and the recovery sequence I wrote for my future self.

Backups Succeeded Nightly, but Offsite Uploads Failed for 3 Weeks
cron ran every night, local files were complete every day, and the log said done, but offsite storage had not changed for 3 weeks. The first 8 nights of backups were already gone for good because local rotation had deleted them first. This article examines the two gaps that let the failure stay silent for so long.

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.

Thai Steel Framing Conventions for Buildable Drawings
A steel frame model that looks good on screen but makes a fabricator shake their head usually gets a few conventions wrong—member order, stock sections, stacking instead of interweaving, purlins that must be open C sections, and bearing edges on sloping beams that cause steel to intersect when chosen incorrectly.

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.

Import Land Parcel Boundaries from Government Maps into SketchUp
A SketchUp model with Add Location has satellite imagery but no parcel boundaries. This article covers fetching boundaries through OGC-standard WFS, where viewparams is required to avoid getting zero features, converting UTM47 coordinates to WGS84, and placing them in the model with latlong_to_point to line up with the imagery.

Driving AutoCAD from PowerShell via COM — Scripted Drafting
AutoCAD exposes COM for scripting just like Excel. Connect to the running instance with New-Object, then draw lines, circles, text, and 3D boxes, create layers, add dimensions, and save files entirely from PowerShell, with four traps I hit myself and learned never to repeat.

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.

Auditing CCTV Directly with ISAPI and ffprobe RTSP, Bypassing the NVR
The NVR screen only tells you a camera is online. It does not tell you its model, how old its firmware is, or the actual stream resolution. In this article, I write a script to query ISAPI deviceInfo on each camera directly, then use ffprobe to read values from the actual RTSP stream to build an inventory and check the health of the camera system I maintain.

Planning 2.4GHz WiFi Channels 1/6/11 and Multi-Floor Heatmaps
A five-story building with seven APs per floor needs a 2.4GHz channel plan that avoids overlap both horizontally and vertically. This article covers the 1/6/11 principle, per-floor channel tables, the actual Tx power and RSSI threshold settings, and a Python script that labels floor plans with channels using PIL before a walkthrough survey to create a heatmap.

Inventory Switch and AP Ports with SNMP Walk and MAC Tables
You can build your own Layer 2 inventory without a controller or NMS. Write an SNMP walk with pysnmp asyncio to retrieve sysDescr, ifTable, and the bridge FDB, then convert OID indexes back into MAC addresses to find which device connects to which port on which switch.

Building a Single-Screen Ping Monitor with C++, ImGui and DirectX 11
A small app that pings every device on the network at once and puts the results on one screen. It sends ICMP directly through iphlpapi without calling ping.exe, runs one thread per host, communicates with the UI through std::atomic, renders with Dear ImGui on DirectX 11, and builds with CMake and a single PowerShell script.

Read Device Uptime Without SNMP/SSH/API Using TCP Timestamps
Many IP cameras, door controllers, and embedded devices have no SNMP, SSH, or API for querying uptime. But if they return TCP timestamps, I can read their boot time remotely without logging in even once.

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.

Encrypt Secrets in .env and Decrypt Them When Config Loads
How to store passwords and API keys as ciphertext in Laravel's .env and have config decrypt them on load, using a single helper that detects encrypted values, plus the APP_KEY mismatch across environments that can bring everything down if you get it wrong.

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.

Laravel Behind a Proxy Sees 127.0.0.1 — Silent Form, No Error
A contact form on DirectAdmin says the sender is not human, sends no mail, and leaves every log silent — the cause is Laravel behind nginx-to-apache seeing $request->ip() as 127.0.0.1, failing the bot check and returning before the mail is sent, with an explanation of why TrustProxies must not use '*'

WAF Returns 406 for Thai Text in POST: Fixing It Without Root
ModSecurity on the host returns 406 for certain Thai sentences in the POST body, preventing forms from saving — but as a hosting tenant, I cannot change the rules myself. This article shows how to isolate the offending text through black-box testing, then work around it with JS encoding on submit + middleware decoding in Laravel.

My Firewall Permanently Banned Googlebot and Hid Sites from Search
I investigated why sites disappeared from search results and found Googlebot in my own firewall's permanent ban list. 408 of 500 entries came from the counter tallying WAF false positives. How I lifted the bans, wrote a script to fetch official bot IP ranges into the ignore list daily, and dealt with a grep trap and the side effects of blocking entire countries.

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.

AI Crawler Overloads ?add-to-cart= — Block It Without Hurting SEO
Server load triggered an email alert. The logs showed that 84% of the day's traffic came from one AI crawler following add-to-cart links for every product. I added layers of protection with robots.txt, rel=nofollow and nginx limit_req while keeping all product pages indexable.

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.

Factory to Cloud Behind NAT: Push Snapshots and a Command Queue
The production machine sits in a LAN behind NAT with no public IP, but the owner wants to view data on a cloud website and make edits from a phone that reach the internal machine — possible without opening ports or setting up a VPN, using push snapshots + an edit command queue + idempotent acks

Repairing a Claude Code Session .jsonl File Too Large to Open at 38 MB
A single session stretched over 8 days grew to 38.61 MB / 11,537 records until the UI could no longer open its history. I wrote a Python script to process it line by line, trimming only the parts that were already unreadable to humans, bringing it down to 17.41 MB while preserving all 8,171 message uuid values in their original order, with no orphans.

Stolen Mailbox Password Sent 1,786 Spam Emails Unseen by CSF/LFD
A real case of a stolen mailbox password used to send 50-75 spam emails a day, below a limit of 200, for 4 straight weeks without a single response from CSF/LFD/BFM because they only count failed authentication attempts, with the decisive question of whether an account was compromised or merely under attack, exim forensic commands, and a blocklist removal playbook

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.

Migrating vBulletin 4.2.5 to Flarum: Lessons from 200,000 Posts
Migrating an old vBulletin 4.2.5 forum with over seven thousand members, over twenty thousand threads, and over two hundred thousand posts to Flarum — notes on pitfalls that cost real time, from guest userid 0 breaking FK constraints across an entire batch, nested md5 hashes for old passwords, and converting BBCode to s9e XML, to attachments stored on disk instead of in the DB and why I had to scp scripts instead of using heredoc

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.

Set DirectAdmin Admin Backup Cron from the CLI Without the Panel
CMD_ADMIN_BACKUP takes five steps to set up a cron job, but DirectAdmin actually just appends one line to backup_crons.list and lets dataskq pick it up. Here is how to write directly to the file from the CLI, including common traps with when=now, base64, and append_to_path.

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.

MDES 2564 Government CCTV Price Limits and a Hikvision PoE BOM
Read the full MDES June 2564 government CCTV reference pricing and basic specifications before setting a budget, then design a Hikvision IP/PoE BOM that meets the minimum specifications, reference price ceilings, 30-day recording retention rule, and requirement to allow at least 3 brands to compete.

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.

Control TP-Link Omada OC200 via REST API with Python
A practical guide to logging into an OC200 controller through REST API v2 with Python — handling CSRF tokens, self-signed certs, fetching sites/devices/SSIDs/DHCP/port-forward rules, and writing back with PATCH, including the read-only fields that cause a nonzero errorCode

Network-as-code on MikroTik RouterOS
A network-as-code approach for MikroTik — save /export output as a file, strip secrets with a sanitize script before sharing, split it into sections, then generate configs for multiple sites from Jinja2 templates + one set of variables

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

Screenshot Multiple Domains Directly from Origin, Bypassing Cloudflare
Need screenshots of dozens of domains for inspection, or want to confirm that the origin server (not the CDN cache) renders correctly — but Chrome's screenshot flag has been broken since version 132, and AV reverts hosts edits? This article uses Playwright to override DNS at the process level, solving both problems.

~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.

Auditing a Deployed Laravel Site — 3 Layers in Priority Order
After deploying Laravel to production, check security in 3 layers — probe externally for exposed endpoints, check files/permissions on the server, then grep for vulnerabilities in the code. This article provides commands and P0-P2 priorities for deciding what to fix first.

Mail Stopped After Moving to Cloudflare — Proxied Mail Records
After adding a domain to Cloudflare and pointing DNS at the host, incoming mail suddenly stops — the most common cause I see is leaving mail/smtp/pop records proxied (orange cloud), even though Cloudflare's proxy only handles HTTP/S. This article covers which records must be grey + token/SSL pitfalls.

nginx heap overflow spans 15 years—version checks aren't enough
CVE-2026-42533 affects nginx versions 0.9.6 through 1.31.2, spanning 15 years, but actual exposure depends on the config, not the version. The problem is that news reports describe narrower conditions than the real ones. Checking against those reports can return "safe" even when the config still meets the conditions.

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.

Read-only Debian shared-host security audit — avoid banning yourself
Audit the security of a Debian server running DirectAdmin in read-only mode first — but the first trap is that scan traffic itself triggers CSF's brute-force rules and gets your own IP banned. This article lays out a safe audit sequence + areas to check in 2024-2026

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

libmodsecurity3 + nginx — only ctl:ruleEngine exclusions work
ModSecurity v3 (libmodsecurity3) + nginx + OWASP CRS — the ctl:ruleRemoveById taught in the guides does nothing and fails silently. This article covers what I found after trying every approach: ctl:ruleEngine works, scoped with a Host+URI chained rule, and the exclusion file must load before CRS.

Mail Checks Ban the Whole Office—Fixing It with Login Auto-Whitelist
Real mail clients behave exactly like bots — office machines keep retrying old passwords until CSF bans the whole office. This article covers an auto-whitelist system that watches for successful logins + Thai GeoIP, then grants a temporary allow for 24 hours, along with 5 pitfalls I learned the hard way.

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

LAN Access for a Team of 7 Without Open Ports: Cloudflare Zero Trust
A team of 7 needs RDP access to machines on the LAN without opening any ports to the internet — the Cloudflare Zero Trust architecture (WARP + cloudflared), with 3 lessons from implementation: a forgotten IdP setup, Gateway defaults that allow everything, and the default-deny rule people forget most often

WordPress 500s, Empty debug.log — The Fatal Is in the Apache Log
WordPress returns 500 only on portfolio pages, but debug.log is empty — because the fatal error during theme rendering happens before WP logging is ready. The answer is in the apache error log (proxy_fcgi), along with a fix for a child theme with a stale template copied from an older parent version.

AdSense Says ads.txt Is Missing: Tracing the Logs Layer by Layer
The AdSense Sites page reports ads.txt as "Not found" even though the file exists and is correct — a forensic investigation using curl + access logs to prove, layer by layer, whether Googlebot visited and what it received. Case closed: nothing was blocking it; Google's own status was stale.

Recovering Old Website Images with Wayback Machine CDX API + Python
A website over a decade old had broken images throughout because it hotlinked to a dead CDN — I wrote a Python script to list images from the Wayback Machine CDX API and restore them to their original paths without touching a single character of HTML, with the full script I actually used.

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

Lost the .com: Move to .net on DirectAdmin Without Data Loss
A .com domain expired and ended up with a domain squatter — how I moved the whole site (files/mail/DNS/SSL) to a new .net domain on DirectAdmin with a single CMD_CHANGE_DOMAIN command, plus 4 follow-up tasks that do not happen automatically

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.

Edit Videos with Claude Using Thai Prompts, No Video Editor Needed
How I actually use Claude in VS Code to trim, resize, join, and add background music to videos by typing in Thai, without memorizing ffmpeg commands or opening a video editor. Includes getting AI to compose music, NVIDIA GPU acceleration, and Mac and Linux instructions for people who have never used Claude.

Managing Linux Servers Safely with AI over SSH
How I actually use Claude to audit and fix Linux server security over SSH, from preparing my machine and storing secrets to hard rules before touching production

Put an Idle VPS to Work with WireGuard — A Complete Client Guide
Turn a VPS you signed up for on an annual plan into a WireGuard hub for accessing your home LAN anytime, without port forwarding — covering app installation, config imports, QR scanning, troubleshooting, and administration

Claude Builds a Steel Shed in SketchUp, Then Runs FEA and a Heatmap
In the final part of the series, I ask Claude to build an entire mono-slope steel shed in SketchUp, layer by layer from concrete columns to roof tiles, then analyze its strength with PyNite and apply a heatmap based on the actual forces.

Driving SketchUp with Claude Code, Part 2 — MCP Plugin Bug Fixes
Let Claude Code actually "see" and "drive" SketchUp through a community MCP plugin, with fixes for 2 time-wasting bugs — a broken PyPI wheel and a UI freeze caused by a blocking socket

Driving SketchUp with Claude Code, Part 1 — uv and PyNite Setup
Start a series on using Claude Code to draw 3D steel structures in SketchUp and run FEA calculations. This first part covers setup only, configuring Claude permissions, installing uv, and creating a conda env with PyNite.

Auto-Unblock the Office in CSF with a Geo+Account Gate
A real case of a client office whose IP changes daily, with a ghost device using an old password that repeatedly gets the whole office blocked by CSF. How I automated unblocking with a narrow account+geo gate that lets the real office through while keeping foreign credential attacks blocked.

A Telegram Bot for DA Server Monitoring with a Small Blast Radius
A guide to setting up a Telegram bot as a lightweight control surface for monitoring a DirectAdmin server — read-only commands plus IP blocking/unblocking only, with no open ports, no added services, and a blast radius limited by least privilege at every layer

Off-Site Backups with Pull Architecture + rclone/rsync
Add off-site Layer 3 to complete 3-2-1 with pull architecture (the destination host fetches the data), rclone + rsync, and lessons from real pitfalls involving NAT, RSA host keys, cwRsync, and PID recycling.

Rebuilding DirectAdmin Backups with NFS and POSIX ACL in Production
A record of rebuilding backups on a DirectAdmin server after migrating to a new machine — from FTP push that broke at three layers to an NFSv4 hard mount + POSIX ACL, with kernel-level lessons learned along the way.

Reading exim mainlog for Stuck Clients — Mail Forensics Part 1
How I read /var/log/exim/mainlog to distinguish an office machine using the wrong password from an attacker running a brute force attack, with the commands I use when customers need answers fast.

Tracing Dovecot IMAP/POP Failures on DirectAdmin and Keeping 90-Day Logs
A follow-up to Mail Forensics — trace IMAP/POP failures in /var/log/mail.log on DirectAdmin, distinguish stuck clients from attackers using cross-protocol ratios, and adjust logrotate to keep a full 90 days of logs instead of defaults that lose them within days

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.

Per-User Redis on DirectAdmin Without Buying pro_pack
How to set up per-user Redis object cache on DirectAdmin shared hosting with a systemd template + unix socket + cgroup limit, using a custom provisioning script instead of the DA panel that requires a pro_pack license, with connection recipes for WordPress, Flarum, and Laravel

Tuning ModSecurity on DirectAdmin for Shared Hosting
The 4 additions I made beyond DA defaults — exclusions before CRS, a 1-hit credscan ban, a fix for the incorrect MODSEC_LOG path shipped by DA, and a self-heal cron to recover from rebuild overwrites

DirectAdmin from CentOS 7 to Debian 12 on Proxmox (Series Index)
Notes on migrating a DirectAdmin shared host from CentOS 7 (EOL) to Debian 12 on Proxmox, serving 80+ customers, split into 5 parts with an overview of the process and links to every part

DA Migration Finale — Security Fixes and Hardening After Stabilizing
Wrapping up the cross-distro DirectAdmin migration series with security work after the server stabilized — sudoers wildcard CVE, OpenSSL SECLEVEL, ModSec exclusions before CRS, a csfpost drop-in, and 5 layers that survive DA updates

Moving DA from CentOS 7 to Debian 12, Part 4 — Cutover and 17 Issues
The actual cutover, where the near-0 downtime plan fell apart because of the DA Legacy license and slow qcow2 reads, ended with 1 hour of downtime and 17 issues in quick succession after the switch — critical web stack outages, medium issues affecting subsets of services, and minor debugging notes.

DA Migration Part 3 — DirectAdmin + CustomBuild on Debian 12
Installing DirectAdmin through setup.sh, then assembling a stack with multiple PHP versions, MariaDB, ModSecurity, and rspamd using CustomBuild on a staging VM behind NAT, including pitfalls I ran into myself.

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.

DA CentOS 7 → Debian 12 on Proxmox Part 1 — NAT pre-stage
Preparing a new VM on an internal bridge with NAT while the old VM continues serving customers, reducing downtime when migrating DirectAdmin from CentOS 7 to Debian 12 on Proxmox.

CSF Auto-Unblock for Ghost Devices Amid Botnet Brute Force
A real case on a DA shared host where a ghost device in an office got the entire office blocked by CSF every morning. I explain a 4-layer auto-unblock setup that distinguishes office devices from a botnet without accidentally unblocking attackers.

OpenClaw vs. VSCode + AI Agent: Which Should You Use?
Comparing two approaches that turn AI from a chatbot into an assistant that gets work done — a chat-app gateway like OpenClaw and an editor-based setup like VSCode + AI Agent, covering workflows, token costs, and security basics for beginners.

Build a Desktop AI Server with WSL2 + Ollama + OpenClaw
A record of building a home AI server from scratch, starting with Windows 11 IoT, installing WSL2 + Ubuntu, setting up Ollama to run LLMs on two GPUs, then adding multiple OpenClaw instances through systemd, covering drivers, custom Modelfiles, user isolation, and benchmarks