megacolorboy

Abdush Shakoor's Weblog

Writings, experiments & ideas.

Centralizing Storage for Multiple Websites by Mounting a Shared Windows Network Drive on CentOS Using CIFS

Running more than 45 websites on a CentOS server can be a challenge, especially when it comes to managing storage. In my case, these websites are hosted across two different servers, and each site has its own storage folder for images and uploads.

To streamline this, I came up with a solution: centralizing all storage uploads in one place by hosting a shared Windows network drive and mounting it on the CentOS servers.

This way, all websites can access the same centralized storage, making management much easier.

Here’s how I did it, step by step:

Step 1: Install cifs-utils

First, you need to install the cifs-utils package, which provides the necessary tools to mount CIFS shares.

yum install cifs-utils

Step 2: Create a Service Account

Next, I created a service account on the CentOS server. This account will be used to access the shared drive. I named it svc_library_core and assigned it a UID of 5000. You can choose any name and UID that isn’t already in use.

useradd -u 5000 svc_library_core

Step 3: Create a Group for the Share

I also created a group on the CentOS server that will map to the shared drive. This group will contain all the Linux accounts that need access to the share. I named the group share_library_core and gave it a GID of 6000.

groupadd -g 6000 share_library_core

Step 4: Add Users to the Group

After creating the group, I added the necessary users to it. For example, if you want the apache user to have access to the share, you can add it like this:

usermod -G share_library_core -a apache

Step 5: Prepare Credentials

To mount the drive, you’ll need to provide the credentials for the Windows share. You can either pass them directly using the -o option or save them in a file. I opted to save them in a file for security reasons.

Switch to the root user and create a file to store the credentials:

su - root
touch /root/creds_smb_library_core
chmod 0600 /root/creds_smb_library_core
vi /root/creds_smb_library_core

Add the following lines to the file, replacing the placeholders with your actual Windows or Active Directory username and password:

username=YourWindowsUsername
password=YourWindowsPassword

Step 6: Mount the Drive

Now, you’re ready to mount the shared drive. Use the following command, replacing the placeholders with your actual values:

sudo mount -t cifs //<destination_ip>/path/to/directory /test-directory -o credentials=/root/creds_smb_library_core,uid=5000,gid=6000,iocharset=utf8,file_mode=0774,dir_mode=0775
  • //<destination_ip>/path/to/directory is the path to the shared drive on the Windows server.
  • /test-directory is the local directory where you want to mount the drive.
  • credentials=/root/creds_smb_library_core points to the file containing your credentials.
  • uid=5000 and gid=6000 map the mounted drive to the service account and group you created earlier.
  • iocharset=utf8 ensures proper character encoding.
  • file_mode=0774 and dir_mode=0775 set the file and directory permissions.

Step 7: Unmounting the Drive

If you need to unmount the drive later, you can do so with the following command:

sudo umount /test-directory

Bonus: Automount Script for Server Reboots

One challenge I faced was ensuring that the shared drives were automatically remounted in case the servers went down or were rebooted. To handle this, I wrote a simple script that mounts the drives on startup.

Create the Script

Create a new script file, for example, /usr/local/bin/mount_shares.sh:

#!/bin/bash
mount -t cifs //<destination_ip>/path/to/directory /test-directory -o credentials=/root/creds_smb_library_core,uid=5000,gid=6000,iocharset=utf8,file_mode=0774,dir_mode=0775

Make sure to replace the placeholders with your actual values.

Make the Script Executable

Set the executable permission on the script:

chmod +x /usr/local/bin/mount_shares.sh

Add the Script to Startup

To ensure the script runs on startup, add it to the /etc/rc.local file:

/usr/local/bin/mount_shares.sh

Make sure /etc/rc.local is executable:

chmod +x /etc/rc.local

Test the Script

Reboot your server and verify that the shared drives are automatically mounted.

And that’s it!

You’ve successfully mounted a shared Windows network drive on CentOS using CIFS and ensured it remounts automatically on server reboots.

This setup has made managing storage for multiple websites much more efficient and centralized.

Hope you found this article useful!

Adding Timezones to DateTime Strings in C#

Whether you're dealing with UTC, local times, or specific timezone offsets, there's a simple way to format your DateTime objects properly.

Here’s the rundown:

1. Using DateTimeOffset for Timezone-Aware DateTimes

DateTimeOffset is my go-to for working with date and time when I need to include the timezone. It represents both the date, time, and the offset from UTC.

Example: Adding UTC or Specific Timezones

DateTime utcDateTime = DateTime.UtcNow;
DateTimeOffset utcWithOffset = new DateTimeOffset(utcDateTime, TimeSpan.Zero); // UTC timezone

// For a specific timezone, like Eastern Standard Time (UTC-05:00)
TimeZoneInfo easternZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTimeOffset easternTime = TimeZoneInfo.ConvertTime(utcDateTime, easternZone);

Console.WriteLine(utcWithOffset.ToString("o")); // 2025-01-23T12:00:00.000+00:00
Console.WriteLine(easternTime.ToString("o"));  // 2025-01-23T07:00:00.000-05:00

The "o" format is particularly handy because it outputs the ISO 8601 standard, which is great for APIs or data exchange.

2. Appending Timezones to Strings Manually

Sometimes, all you need is a DateTime string with a simple timezone indicator (like +00:00 for UTC). Here's how I did that:

DateTime dateTime = DateTime.Now;
string formattedDate = dateTime.ToString("yyyy-MM-ddTHH:mm:ss.fff") + "Z"; // Z = UTC
Console.WriteLine(formattedDate); // Outputs: 2025-01-23T12:00:00.000Z

For more precision, you can include the actual offset:

string formattedDateWithOffset = dateTime.ToString("yyyy-MM-ddTHH:mm:ss.fffzzz");
Console.WriteLine(formattedDateWithOffset); // Outputs: 2025-01-23T07:00:00.000-05:00

3. Custom Format Specifiers for Timezones

Need a custom date string with the timezone? The "zzz" specifier has you covered:

DateTime now = DateTime.Now;
string dateTimeWithTimeZone = now.ToString("yyyy-MM-ddTHH:mm:ss.fff zzz");
Console.WriteLine(dateTimeWithTimeZone); // Outputs: 2025-01-23T07:00:00.000 -05:00

Why you should do this?

  • It’s straightforward to ensure your timestamps are timezone-aware.
  • You can easily adjust for specific timezones or stick with UTC for consistency.
  • The formatting options ("o", "zzz", etc.) give you flexibility to match the needs of APIs, logs, or user-facing systems.

If you've ever struggled with timezone handling in C#, give these approaches a shot. Timestamps are tricky, but with these tools in your belt, you'll nail it every time!

Hope you found this article useful!

A Faster Way to Apply Linux Permissions for Large Laravel Projects Using xargs and find

Today, I learned a more efficient way to apply Linux permissions to large projects by leveraging the power of xargs and find. This method is particularly useful when dealing with Laravel projects that have many files and directories, as it allows for parallel processing and excludes specific folders (like a storage directory) from the permission changes.

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

1. Set Ownership of the Entire Project Folder

To ensure the correct ownership for the entire project, I used the chown command recursively:

sudo chown -R [username]:[groupname] [foldername]

This command sets the owner and group for all files and directories within the specified folder.

2. Set Permissions for the Project Folder and Its Top-Level Directories

Next, I applied the desired permissions to the project folder and its top-level directories using chmod:

sudo chmod 2775 [foldername]

The 2775 permission sets the folder to allow read, write, and execute for the owner and group, and read and execute for others. The 2 enables the setgid bit, which ensures that new files and directories inherit the group of the parent folder.

3. Find and Change Permissions for All Directories (Except the Storage Folder) in Parallel

To apply permissions to all directories while excluding the storage folder, I combined find, xargs, and chmod:

find [foldername] -path [foldername]/storage -prune -o -type d -print0 | sudo xargs -0 -P 2 chmod 2775
  • find [foldername] -path [foldername]/storage -prune -o -type d: Finds all directories except the storage folder.
  • -print0: Outputs the results in a null-terminated format to handle spaces in filenames.
  • xargs -0 -P 2: Processes the results in parallel (2 processes at a time) for faster execution.
  • chmod 2775: Applies the desired permissions to the directories.

4. Find and Change Permissions for All Files (Except the Storage Folder) in Parallel

Similarly, I applied permissions to all files while excluding the storage folder:

find [foldername] -path [foldername]/storage -prune -o -type f -print0 | sudo xargs -0 -P 2 chmod 0664
  • -type f: Finds all files.
  • chmod 0664: Sets read and write permissions for the owner and group, and read-only for others.

5. Handle the Storage Folder Separately (If It’s Too Large)

For the storage folder, I applied permissions separately to avoid overloading the system:

find [foldername]/storage -type d -print0 | sudo xargs -0 -P 2 chmod 2775
find [foldername]/storage -type f -print0 | sudo xargs -0 -P 2 chmod 0664

This ensures that the storage folder and its contents have the correct permissions without affecting the rest of the project.

Why This Approach?

Using xargs with -P allows for parallel processing, which speeds up permission changes significantly for large projects. Additionally, excluding specific folders (like storage) ensures that sensitive or frequently accessed directories are handled separately.

This method has saved me a lot of time and effort when managing permissions for large projects.

Hope you found this article useful!

Using records for DTOs

If you're building an application or API using .NET Core in where you might have to integrate with any external services or third-party APIs, this is for you.

What are records?

It's a feature introduced since C# 9.0 that allows you to create simple and immutable data types. When representing DTOs (Data Transfer Objects), it comes in handy because the syntax in concise and it's primarily used to transfer data between layers of an application such as the Core and Presentation Layer.

Here's an example of a car using a regular class type:

public class Car
{
    public string Manufacturer { get; init; }
    public double Price { get; init; }
    public string Color { get; init; }
}

And here's a simplified version of that using record type:

public record Car(
    string Manufacturer,
    double Price,
    string Color
);

Making use of the record type to manage Data Transfer Objects makes use of clean coding practicies by writing concise yet maintainable code.

So, no need of using classes?

Well, it depends because records aren't meant to replace classes for all scenarios. It's still recommended to use regular classes for complex types with complex behavior and whereas, records can be used for simple data structures.

Hope you found this article useful!

Using Property Pattern Matching in C#

Alright, we've all been there especially when it comes to writing IF conditions with multiple checks in a single line like the example below:

if(employee != null && employee.Age > 20 && employee.Address.City == "Dubai")
{
    // Do something here...
}

As you can see, this condition is straight-forward but can sometimes hinder readability and doesn't really ensure type safety.

Here's where you can make use Property Pattern Matching.

What's Property Pattern Matching?

This type of pattern matching is used to match the defined expression if the conditions/nested conditions successfully matches the corresponding property or the field of the result and output of the result is not null. Here's an example of how it might be like:

if(employee is { Age: > 20 && Address.City == "Dubai" })
{
    // Do something here...
}

This pattern elevates your code's readability, ensures type safety and enable flexibility in terms of data manipulation.

Hope you found this tip useful!

Remove Control Flags

There are times when we write flags to control the flow of the program. I mean, it's good to use control flags (sometimes) but it's not necessary to be used in most cases. Like the one shown below:

public bool HandlePaymentRecords(List<PaymentRecord> paymentRecords)
{
    bool isSuccessful = true;

    foreach(var paymentRecord in paymentRecords)
    {
        if(!ProcessPaymentRecord(paymentRecord))
        {
            isSuccessful = false;
            break;
        }
    }

    return isSuccessful;
}

The name of method states it's purpose but it seems a tad bit complicated and might confuse some developers who get on-board with it.

Instead, you can make of use of a refactoring technique called Remove Control Flags and modify the method to look like this:

public bool HandlePaymentProcess(List<PaymentRecord> paymentRecords)
{
    foreach(var paymentRecord in paymentRecords)
    {
        if(!ProcessPaymentRecord(paymentRecord))
        {
            return false;
        }
    }

    return true;
}

The logic is the same but now the code is now simplified, less complex to read and much more maintainable.

Less code is good code.

Hope this helps you out!

Error loading V8 startup snapshot file in Visual Studio Code

Unusually, one fine afternoon, I was facing issues with opening my VSCode editor and I faced the followinge error when I tried code . on my project directory:

[0830/101630.031:FATAL:v8_initializer.cc(527)] Error loading V8 startup snapshot file

I didn't really understand what might have caused it but after reading this issue raised on GitHub, I realized that occurred due to a corrupted update while I was shutting down my PC (Good job, Windows! :facepalm:)

If you're are lucky enough, here's how you can get it back to work:

  1. Go C:\Users\XXX\AppData\Local\Programs\Microsoft VS Code directory
  2. If you spot a folder titled _, copy the contents and paste it in the VS Code directly
  3. Try starting your editor again

The following steps worked out for me and if it worked out for you, that's great! Else, you'll have to re-install your editor all over again, which means you might have to re-install your extensions and re-configure it again (if you haven't taken a backup of it).

Hope this helps you out!

Cicada 3301

A short docu-series about one of the most mysterious puzzles on the internet.

Ever heard of Cicada 3301? It's one of those mysterious puzzles that made every crypto enthusiast around the globe turn into super sleuths hunting for clues and chasing the next puzzle. I was quite interested in it after watching this series of documentaries titled Cracking the Code of Cicada 3301 that shot by Great Big Story, which made my interest towards my cryptography grow larger.

Although, I never tried this series of puzzles myself, the actual host of this puzzle is unknown as some claimed that it could be a recruitment programme by the FBI/CIA/NSA/GCHQ to hire super smart individuals or maybe some crypto/security group that cares about data privacy and security.

I remember watching this series back in 2020 and it was quite hard for me to find these videos and hence, I'm sharing it here for you and my reference as well (in case, I wanted to re-watch the documentary again):

What I liked about this documentary series is how it connects people of various disciplines such as Mathematics, Programming, Linguistics and Steganography to deduce and decipher a really complex puzzle. I just find that inspiring because knowledge is power and when aligned with people of similar interests and ambitions, you build new bridges of friendship that spans around the globe.

Hope you liked reading this article.