megacolorboy

Abdush Shakoor's Weblog

Writings, experiments & ideas.

DateTimeKind.Unspecified can quietly break your dates

Two months ago, a client raised a critical ticket where some users complained that their Start Date of the Financial Year had gone one day backward. This wasn’t caught during UAT while integrating their API, and I was surprised to run into this date conversion bug while I was on vacation (yes, that sucks!).

I had a perfectly normal-looking date:

2025-01-01 00:00:00.0000000

Nothing fancy. But after converting it to UTC, I noticed something odd — the date changed.

Luckily, I was able to trace where it was coming from, and after debugging in Visual Studio 2022, it turned out the culprit was DateTimeKind.Unspecified.

When a DateTime is parsed without timezone information, .NET marks it as Unspecified. If you then call ToUniversalTime(), .NET assumes the value is local time and converts it to UTC. On a UTC+5:30 system, that means:

2025-01-01 00:00 → 2024-12-31T18:30:00Z

Same instant, different calendar date. Easy to miss, painful to debug.

The fix

If your date is already meant to be UTC, you need to say so explicitly:

DateTime.SpecifyKind(inputDateTime, DateTimeKind.Utc);

Or handle it defensively in one place:

switch (inputDateTime.Kind)
{
    case DateTimeKind.Utc:
        return inputDateTime;

    case DateTimeKind.Local:
        return inputDateTime.ToUniversalTime();

    case DateTimeKind.Unspecified:
    default:
        return DateTime.SpecifyKind(inputDateTime, DateTimeKind.Utc);
}

Takeaway

Never leave DateTimeKind to chance especially if you’re working with APIs, audits, or anything date-sensitive.

It’s one of those small details that only shows up when things go wrong — which makes it worth handling upfront.

Hope you found this tip useful!

Resize images from a file list using ImageMagick

Today, a colleague of mine uploaded a large number of images, only to realise later that they were all uncompressed and the site was loading noticeably slower.

His first thought was to compress them and re-upload everything again—but why do something redundant when you can handle it easily from the terminal using ImageMagick?

Previously, I’ve written about how ImageMagick makes it easy to resize images in bulk. However, sometimes you don’t want to touch every image in a directory.

In cases like this, if you already know which images need resizing, you can list their filenames in a text file and let ImageMagick process only those.

Assume a files.txt like this:

image1.jpg
photo_02.png
banner.jpeg

You can then resize just those images while keeping the original aspect ratio intact and avoiding upscaling:

while IFS= read -r file; do
  [ -f "$file" ] && mogrify -resize 500x600\> "$file"
done < files.txt

This works well when cleaning up large media folders or fixing legacy content where only a subset of images needs adjustment.

Hope you found this tip useful!

Block browser features with permissions policy in Nginx

Recently, I learned that you can explicitly disable browser features like camera, microphone, and geolocation using the Permissions-Policy HTTP response header.

Using a single line in nginx, it does the job:

add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

What this does?

  • Disables camera access
  • Disables microphone access
  • Disables geolocation access
  • Applies to all origins
  • The browser won’t even prompt the user for permission

The empty () means no origins are allowed to use these features.

Why this is useful?

  • Improves security and privacy
  • Prevents misuse by third-party scripts
  • Good default for content sites, admin panels, and APIs

The always flag ensures the header is sent even on error responses (404, 500, etc.).

Hope you found this tip useful!

Ubuntu Bootloader Recovery

This is a guide on how to recover Ubuntu's GRUB Bootloader in the case of whenever a LVM UUID is changed or corrupted.

The Problem

The error "disk not found: /xxx-xxx" in grub rescue> mode suggests GRUB can't find the device or logical volume it was previously configured to boot from.

This usually happens when: * LVM volumes weren't activated during boot * The volume group UUID changed or became corrupted

The Context

The /xxx-xxx in the message is likely referring to a LVM volume by UUID, e.g.:

error: disk 'lvmid/XXX-XXX-XXX' not found

This typically means GRUB is referencing a missing or renamed LVM LV or VG

The Solution

Boot from a live ISO, open a terminal and follow these steps:

1. Check Disks and LVM State

sudo lsblk
sudo fdisk -l

Then:

sudo vgscan

This activates any found LVM volume groups and logical volumes.

Then verify with:

sudo lvdisplay

3. Mount the System Manually

Assuming your root volume is /dev/mapper/your_lvm_drive:

sudo mkdir /mnt/recovery
sudo mount /dev/mapper/your_lvm_drive /mnt/recovery

4. Prepare for chroot

sudo mount --bind /dev /mnt/recovery/dev
sudo mount --bind /proc /mnt/recovery/proc
sudo mount --bind /sys /mnt/recovery/sys
sudo chroot /mnt/recovery

The chroot command changes the root directory for the kernel, effectively making a specified directory the new starting point for any file access within the chrooted process.

5. Reinstall GRUB

grub-install /dev/sdX  # Replace sdX with the actual disk (like /dev/sda)
update-grub

Exit chroot mode, unmount the recovery path from live-boot OS and reboot the system:

exit
sudo umount -R /mnt/recovery
sudo reboot

Hope you found this article useful!

Eliminate Laravel .env leakage between multiple sites on Apache (Windows)

Today, I learned something pretty important (and frustrating until I figured it out): if you're running multiple Laravel applications on Apache (Windows) using mod_php, you can run into a strange bug where one site's .env settings override the other.

This became painfully obvious when I noticed that one of my Laravel sites was randomly connecting to the wrong database, even though the configurations in each .env were different. Weird, right?

Turns out, the culprit was mod_php.

The problem

I had two Laravel sites hosted on the same Windows machine with Apache. Both were running fine — until they weren’t.

The issue? Sometimes one site would pick up the database config of the other. One minute I’d be on Site A, and then somehow its .env settings were coming from Site B.

This is just total chaos and worse, this issue existed from more two years until I decided to dive into the problem.

The cause

Laravel loads .env values during the bootstrap process and caches them in memory. When using mod_php, Apache loads PHP as a module once and shares it across all sites. That means only one copy of Laravel’s environment gets loaded — and reused across both apps.

So even if each app had its own .env, it didn’t matter. Apache was essentially saying:

“Hey, here’s PHP. I already booted Laravel with this config — just reuse it.”

The solution

After some digging, the solution is ditch mod_php and switch to FastCGI (mod_fcgid).

With FastCGI, each site runs its own instance of PHP via php-cgi.exe, which boots Laravel in isolation — meaning each .env file is respected per site.

How to configure it?

First, you need to disable mod_php in httpd.conf:

# LoadModule php_module "C:/path/to/php/php8apache2_4.dll"
# <FilesMatch \.php$>
#     SetHandler application/x-httpd-php
# </FilesMatch>
# PHPIniDir "C:/path/to/php/"

Then install mod_fcgid from Apache Lounge and copy mod_fcgid.so to modules/ directory and enable it in httpd.conf:

LoadModule fcgid_module modules/mod_fcgid.so
Include conf/extra/httpd-fcgid.conf

Next, you have to create/update httpd-fcgid.conf:

<IfModule fcgid_module>
   AddHandler fcgid-script .php
   FcgidInitialEnv PHPRC "C:/path/to/php"
   FcgidWrapper "C:/path/to/php/php-cgi.exe" .php
   Options +ExecCGI
   FcgidMaxProcessesPerClass 150
   FcgidMaxRequestsPerProcess 1000
   FcgidProcessLifeTime 300
   FcgidIOTimeout 120
   FcgidPassHeader Authorization
   FcgidFixPathinfo 0
</IfModule>

And then atlast, update your virtual host:

<VirtualHost *:443>
   ServerName example.com
   DocumentRoot "C:/path/to/website/public"

   <Directory "C:/path/to/website/public">
      Options +ExecCGI -Indexes +FollowSymLinks
      AllowOverride All
      Require all granted
      AddHandler fcgid-script .php
      FCGIWrapper "C:/path/to/php/php-cgi.exe" .php
   </Directory>

   SSLEngine on
   SSLCertificateFile "..."
   SSLCertificateKeyFile "..."
</VirtualHost>

Restart the nginx service and clear the laravel application cache:

httpd -k restart
php artisan config:clear
php artisan cache:clear

Bonus: Fixing 403 Forbidden Errors

After switching to FastCGI, I ran into a 403 Forbidden error on one site. That turned out to be a missing Options +ExecCGI and Require all granted in the <Directory> block. Once added, everything worked perfectly.

Result

Now, each Laravel site runs completely isolated, even though they use the same php-cgi.exe. No more .env bleed. No more wrong DBs. Just clean, independent Laravel environments — as it should be.

Takeaways

  • Don’t run multiple Laravel apps on Apache with mod_php unless you're okay with shared config risks.
  • Use mod_fcgid and php-cgi.exe for true isolation.
  • Even if using the same PHP version, FastCGI will spawn separate processes, so .env loading stays scoped.
  • Always check Apache error logs (error.log) and php_sapi_name() for confirmation.

If you’re running Laravel on Windows with Apache — this fix is a must.

Happy hosting and hope you found this article useful!

Removing Commits from a Branch Using Git Interactive Rebase

I'm working on a project that had approximately five minor incident fixes. However, I got approval from the client side to push only three of them. Luckily, my commits are atomic, so making this build was easier than I thought.

At this moment, I decided to clone the latest branch into a temporary branch and roll back two commits for today's deployment while keeping the latest branch intact with all the fixes.

To achieve this, I used Git interactive rebase, a powerful tool for modifying commit history.

Why Use Interactive Rebase?

Interactive rebase allows you to modify commit history, including:

  • Removing unwanted commits
  • Reordering commits
  • Squashing multiple commits into one
  • Editing commit messages

How I Did It?

Here’s the step-by-step process I followed:

1. Clone the Branch

Before making any changes, I cloned the branch to have a backup:

git checkout -b my-cloned-branch original-branch

2. Start Interactive Rebase

To remove specific commits, I ran:

git rebase -i HEAD~N

Where N is the number of commits I wanted to review. This opened an editor displaying the most recent commits.

3. Modify the Commit List

The interactive rebase interface showed something like this:

pick abc123 Commit message 1
pick def456 Commit message 2
pick ghi789 Commit message 3

To remove a commit, I replaced pick with drop.

To edit or squash commits, I could replace pick with commands like edit or squash.

After making my changes, I saved and closed the editor.

4. Handle Any Conflicts (If Needed)

If Git encountered conflicts, it prompted me to resolve them. I fixed the files and continued the rebase:

git rebase --continue

Key Takeaways

  • Interactive rebase is powerful for cleaning up commit history.
  • Always clone or create a backup branch before rebasing.
  • If working with a team, communicate before modifying shared branches.

This was a great learning experience, and now I feel more confident managing Git history efficiently!

Hope you found this tip useful!

Recovering MySQL After an Accidental Binlog Deletion

Last night, I found myself debugging a MySQL issue at an ungodly hour. The MySQL80 service refused to start, throwing a vague error:

The MySQL80 service started and then stopped…

Digging into mysql.err, I discovered the culprit—someone (not me! 😤) had accidentally deleted a binary log (mysql-bin.000xxx) while MySQL was still tracking it. Since MySQL relies on binlogs for replication and crash recovery, it panicked when it couldn't find the missing file.

The Fix

To get MySQL running again, I had to:

  • Disable binary logging (since this is a standalone server)
  • Delete the corrupt binlog index file (mysql-bin.index)
  • Purge all old binlog files that were hogging disk space

How to Properly Disable Binary Logging in MySQL 8.0?

Since MySQL 8.0 enables binlogs by default, simply removing log-bin=mysql-bin from my.ini wasn’t enough. I had to:

Comment out the existing binlog setting in my.ini

# log-bin=mysql-bin

Explicitly disable binary logging

[mysqld]
skip-log-bin

Delete the old mysql-bin.index file

  • This file keeps track of all binary log files.
  • Since a referenced binlog file was deleted, MySQL would fail to start if the index still contained entries for missing files.
  • Deleting it ensures that MySQL doesn’t look for non-existent logs.

After restarting the service, I confirmed binlogging was off with:

SHOW VARIABLES LIKE 'log_bin';

Output:

log_bin | OFF

In conclusion, crisis averted, disk space reclaimed and finally got some sleep. 😴

Hope you found this useful!

How to Revert a Git Commit on the Server?

Today I learned how to revert a Git commit after pulling it on a server. Whether you've made a mistake or just need to backtrack, Git offers several ways to undo commits depending on your situation. Here's a quick guide on how to handle it.

If you haven't pushed the commit yet

If the commit hasn't been pushed to the remote repository, you can use git reset to undo it.

Soft Reset (keeps changes staged)

git reset --soft HEAD~1

This moves the HEAD back by one commit but keeps your changes staged.

Mixed Reset (keeps changes unstaged):

git reset --mixed HEAD~1

This moves the HEAD back by one commit and unstages your changes.

Hard Reset (discards changes):

git reset --hard HEAD~1

This completely removes the last commit and discards any changes.

Tip: Replace HEAD~1 with HEAD~2 to go back two commits, and so on.

If you've already pushed the commit

Revert the commit (safe for shared repos):

If you've already pushed the commit and want to undo it without rewriting history, use git revert. This creates a new commit that undoes the changes.

git revert <commit_hash>

Find the commit hash using:

git log --oneline

Force Reset (i.e. if you're okay with rewriting history):

If you don't mind rewriting history (and no one else is working on the branch), you can force reset.

git reset --hard <commit_hash>
git push origin HEAD --force

⚠️Warning

This is not recommended if others are working on the same branch as it can cause conflicts.

If you just pulled and want to undo

If you just pulled and want to undo the merge, you can reset to the previous state using ORIG_HEAD.

Undo a merge pull:

git reset --hard ORIG_HEAD

This will bring your repository back to the state before the last pull.

Final thoughts

Reverting commits can seem daunting at first, but once you understand the tools Git offers, it's pretty straightforward. Always double-check before using --hard or --force to avoid accidental data loss!

Hope this helps you get back on track!