megacolorboy

Abdush Shakoor's Weblog

Writings, experiments & ideas.

View list of services in Linux

If you wanted to see a list of services available on your Linux server/desktop, try the following command:

systemctl list-unit-files --type=service

Upon executing this command, you'll be able to see a list of services along with their statuses i.e whether the service is enabled or disabled.

This can come in handy if you want to know the status of a specific service like nginx:

systemctl list-unit-files --type=service | grep "nginx"

Hope you found this tip useful.

Containerize your Laravel application for Development

A short tutorial on how to containerize your Laravel application and use it for testing and development purposes.

Since I started exploring Docker containers during my spare time, I learnt how to containerize a Laravel application using Docker Compose.

In this guide, I'll show you how to containerize your application and by the end of this tutorial, you'll have your application running on four separate service containers:

  • App service running php7.4-fpm.
  • A database service running mysql-80.
  • An NGINX service.
  • A phpMyAdmin service for you to view and manage your database.

Prerequisites

  • You need to have Docker and Docker Compose installed on your system
  • A non-root user with sudo privileges (If you are using any UNIX-based environment).
  • An existing working Laravel project.

I tried this on my personal laptop that runs on Fedora Workstation 36 and hopefully, it should work on Windows and macOS as well. If you are a Windows user, I would recommend you to try WSL2 with Ubuntu installed in it.

Configure your app service

First, you need to create a Dockerfile for your app service in your project's root directory:

touch Dockerfile
vim Dockerfile

In this tutorial, I'll be using PHP7.4 but you can use any version that you need. Here you go:

FROM php:7.4-fpm

# Arguments defined in docker-compose.yml
ARG user
ARG uid 

# Install system dependencies
RUN apt-get update && apt-get install -y \
    git \
    curl \
    libpng-dev \
    libonig-dev \
    libxml2-dev \
    zip \
    unzip

# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*

# Install PHP extensions
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd

# Get latest Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Create system user to run Composer and Artisan Commands
RUN useradd -G www-data,root -u $uid -d /home/$user $user
RUN mkdir -p /home/$user/.composer && \
    chown -R $user:$user /home/$user

# Set working directory
WORKDIR /var/www

USER $user

So, what's happening in the configuration above is that, it pulls a PHP7.4-FPM image from Docker Hub. Next, it'll define the user of the service and install all the necessary dependencies that are given above and set the working directory to /var/www/. The last step will change to the newly created user, which would make sure that you are using the service as a normal user.

Again, if you are a bit playful, try configuring it to however you want it and see if it works.

Set up NGINX and MySQL

I have been following some tutorials and I liked the idea of creating dedicated directories to organize your files related to configuring each service container.

Create a directory named .docker in your project's root directory:

mkdir -p .docker

NGINX

Go to .docker directory and create nginx directory:

mkdir -p nginx

Now let's set up the NGINX service by creating a .conf file:

touch myservice.conf

Now, copy the following configuration to your .conf file:

server {
    listen 80;
    index index.php index.html;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/public;
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
    location / {
        try_files $uri $uri/ /index.php?$query_string;
        gzip_static on;
    }
}

MySQL

Since this is a Laravel application, you can skip this step by running migrations and seeding your database or you can try creating a directory inside the .docker folder to store your MySQL related configuration:

mkdir -p mysql

Once created, create a file named init_db.sql that would contain your entire database dump to be seeded once you run your docker service.

Set up multiple containers using Docker Compose

This the configuration I have used to create four separate services, please take a look and feel free to modify it based on your requirements.

In this configuration, all services will share a bridge network named yourapp which is defined at the bottom of this configuration.

Here is the configuration:

version: '3.8'
services:
  # PHP Service
  app:
    build:
      args:
        user: username
        uid: 1000
      context: ./
      dockerfile: Dockerfile
    image: yourapp 
    container_name: yourapp-app
    restart: unless-stopped
    working_dir: /var/www/
    volumes:
      - ./:/var/www
    networks:
      - yourapp

  # MySQL Service
  db: 
    image: mysql:8.0
    container_name: yourapp-db
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: ${DB_DATABASE}
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_USER: ${DB_USERNAME}
      SERVICE_TAGS: dev 
      SERVICE_NAME: mysql
    volumes:
      - ./.docker/mysql:/docker-entrypoint-initdb.d
    healthcheck:
      test: mysqladmin ping -h 127.0.0.1 -u ${DB_USERNAME} --password=${DB_PASSWORD}
      interval: 5s
      retries: 10
    networks:
      - yourapp

  # NGINX Service
  nginx:
    image: nginx:alpine
    container_name: yourapp-nginx
    restart: unless-stopped
    ports:
      - 8000:80
    volumes:
      - ./:/var/www
      - ./.docker/nginx:/etc/nginx/conf.d/
    networks:
      - yourapp

  # phpMyAdmin service
  phpmyadmin:
    image: phpmyadmin/phpmyadmin:5
    ports:
      - 8080:80
    environment:
      PMA_HOST: db
    depends_on:
      db: 
        condition: service_healthy
    networks:
      - yourapp

networks:
  yourapp:
    driver: bridge

Configure your application

Open your current .env file of your laravel project and just add the necessary database configuration:

DB_HOST=yourapp-db
DB_PORT=3306
DB_DATABASE=your_database_name
DB_USERNAME=your_database_user_name
DB_PASSWORD=your_database_password

Build the application image

After you are done with configuring, run the following command to build your application image:

docker-compose build yourapp

Depending on your network speed, it might take a few minutes to build your image.

Run the application

Once the build is complete, execute the following command:

docker-compose up -d

The following command will run your containers in the background. To display your information about the state of your docker services:

docker-compose ps

Install your application's dependencies:

docker-compose exec yourapp rm -rf vendor composer.lock
docker-compose exec yourapp composer install

And then run the following commands to run your Laravel application:

docker-compose exec yourapp php artisan key:generate
docker-compose exec yourapp php artisan config:clear
docker-compose exec yourapp php artisan cache:clear
docker-compose exec yourapp php artisan view:clear
docker-compose exec yourapp php artisan route:clear
docker-compose exec yourapp php artisan storage:link 

Now, you can go to your browser, type your http://localhost:8000 to access your application.

Conclusion

If you've come to this part, give yourself a pat in the back!

From now onwards, you don't really need to install and set up a local web server and database to run and test your application.

Furthermore, you'll be having a disposable environment in your hand, which means you can easily replicate and distribute it to other developers in your team, so that no one says "It works on my machine and not on yours!"

Hope you liked this tutorial!

Install Docker on Fedora 35/36

Recently, I started to play around with Docker and I thought of installing on my personal laptop which runs Fedora 36 workstation.

If you have Fedora and want to know how to install it, here it is:

Install Docker Engine

First, add the official Docker repositories to your Fedora OS:

sudo dnf install dnf-plugins-core -y
sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo

Then, you can run the following command to install Docker and it's dependencies:

sudo dnf install docker-ce docker-ce-cli containerd.io

During installation, you'll be prompted to import the GPG key in order to install Docker on your system. So, press Y to proceed with the installation.

Next, enable and start the docker service:

sudo systemctl enable docker
sudo systemctl start docker

That's it you are done. You can try running the following command to see if it's installed properly on your system:

sudo docker run hello-world

If it works fine, you should be seeing a "Hello from Docker!" message which means that the installation appears to be working fine.

Hope you found this tutorial useful!

Resolve permission error while SSH-ing to AWS EC2 instance from a Linux machine

If you are someone who's trying to access a AWS EC2 instance via SSH using a private key from a linux machine, you might have or will come across this error:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for 'your-aws-private-key.pem' are too open.
It is recommended that your private key files are NOT accessible by others.
This private key will be ignored.
bad permissions: ignore key: your-aws-private-key.pem
Permission denied (publickey).

Why am I getting this?

From what I have read, EC2 instances will simply not accept a private key that are publicly visible to others especially if it's somewhere stored in your Desktop or Downloads folder.

So basically, your private key should be accessible to others.

Oh, how can I fix it?

It's pretty straightforward, you just have to make sure that the private key is read-only like this:

chmod 400 your-aws-private-key.pem

After that, try connecting again and it should work fine!

Hope you found this tip useful!

Install PFX certificate on a Linux server

I would consider this as an extended post to my previous post that I had written six months ago.

A PFX Certificate usually contains the following in PKCS#12 format:

  • The actual certificate.
  • The private key to the certificate.
  • The Intermediate authority certificate that ensures the trustworthiness of the certificate.

To extract all those files, here are the steps that I have documented:

Note

If the .PFX file prompts you for a passphrase, please check with your project manager or client regarding this information.

Extract the Encrypted Private Key

openssl pkcs12 -in <filename.pfx> -nocerts -out encrypted.key

Extract RSA Private Key

openssl rsa -in encrypted.key -out private.key

Extract Certificate

openssl pkcs12 -in <filename.pfx> -clcerts -nokeys -out certificate.crt

Extract Combined Chain Certificate (Optional)

openssl pkcs12 -in <filename.pfx> -cacerts -nokeys -chain | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > combined_chain_certificate.crt

Once you are done extracting all the required files, you can add the certificates like this:

Apache configuration:

SSLCertificateFile /path/to/certificate.crt
SSLCertificateKeyFile /path/to/private.key

# Optional, if you have it, else skip.
SSLCertificateChainFile /path/to/combined_chain_certificate.crt

Test if it works:

apachectl configtest
systemctl restart httpd

Nginx configuration:

# If you don't have a combined chain certificate:
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;

# If you have a combined chain certificate:
ssl_certificate /path/to/combined_chain_certificate.crt
ssl_certificate_key /path/to/private.key;

Test if it works:

nginx -t
systemctl restart nginx

Hope you found this tip useful.

Install Microsoft SQL Server Driver for PHP on Amazon Linux 2

Recently, I tried to install Microsoft SQL Server driver for PHP on Amazon Linux and searching on how-to do was really annoying.

I read a few articles on StackOverflow and found some samples on GitHub Gists and thought of sharing on how I installed it.

Prerequisites

  1. Amazon Linux 2 installed.
  2. Ensure the ports 80 and 443 are open on your instance.
  3. PHP >= v5.6 and the following extensions: php-devel, php-pear, php-pdo, and php-xml.
  4. Know-how on using the terminal.

Installation steps

These are the commands used to install the SQL Server driver:

sudo su
sudo yum-config-manager --add-repo https://packages.microsoft.com/config/rhel/7/prod.repo
sudo yum update
sudo ACCEPT_EULA=Y yum install -y msodbcsql mssql-tools unixODBC-devel re2c gcc-c++ gcc
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv

Modify php.ini

You can either to go to your php.ini file and add the extension=sqlsrv extension or add it like this:

echo "extension=sqlsrv" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`
echo "extension=pdo_sqlsrv" >> /etc/php.d/30-pdo_sqlsrv.ini

Restart the service

Ensure that the server can connect and restart the service:

sudo setsebool -P httpd_can_network_connect_db 1
sudo systemctl restart httpd && sudo apachectl restart

Next, run the following the command to see that both pdo_sqlsrv and sqlsrv are installed:

php -m | grep "sqlsrv"`

Test if the driver works

Create a test.php file in your root directory and copy-paste this snippet to test if it works:

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

$serverName = "YOUR_DB_HOST";
$connectionOptions = array(
    "Database" => "YOUR_DB_NAME",
    "Uid" => "YOUR_DB_USER",
    "PWD" => "YOUR_DB_PASSWORD"
);

$conn = sqlsrv_connect($serverName, $connectionOptions);

if($conn === false ) {
    print "Connected successfully."; 
} else {
    print "Error while connecting to server.";
}
?>

Hope you found this tip useful!

My thoughts on using Visual Studio Code

My experiences on what made me like using Visual Studio Code.

Ever since my university days, I've always used Sublime Text for any sort of text editing and programming tasks, in general.

Despite the annoying "Subscription" dialog box, I like it because it's lightweight, simple and had nice keybindings that made it much more productive.

This week, I thought of taking Visual Studio Code for a drive, not because many developers are using it but personally, I wanted to know if it can be better than Sublime Text.

The answer is yes, it is and here's what I have experienced so far.

Portability

What I like at first is that, it's cross-platform and has support for different operating systems in various architectures. At work, I use Windows 10 and at home, I use Fedora 35 Workstation and it runs fine on both operating systems.

Intellisense, Intellisense, Intellisense...

Intellisense is a term used by Microsoft that includes various features like: code completion, code hinting, method parameter information and more. By default, the editor supports Intellisense for JavaScript, TypeScript, HTML and CSS. But if you install different programming language extensions like Python, PHP, Golang and so on, you'll be able to configure your editor to have a much more richer experience.

At work, I write PHP code and use Laravel framework to develop web applications, I installed the following extensions to make my coding experience much more productive:

  1. PHP Intelephense (bmewburn.vscode-intelephense-client)
  2. phpfmt (kokororin.vscode-phpfmt)
  3. Laravel Snippets (onecentlin.laravel-blade)

Sublime Text and Vim Keybindings

If you've never used Vim, please go ahead and try. I believe that every programmer should try using VIM instead of fearing the keybindings (like :q) as they were developed for a reason.

But unlike Vim, Sublime Text keybindings are quite fun especially when you want to duplicate a line of code, indent lines of code, matching multiple instances of the same keyword and modifying them with multiple cursors at the same time.

Try installing these extensions and see if you like them:

  1. Vim Emulation for Visual Studio Code (vscodevim.vim)
  2. Sublime Text Keymap and Settings Importer (ms-vscode.sublime-keybindings)

Integrated Terminal

You can use different type of shells like Windows Powershell, Command Prompt, Git Bash and much more. Besides IMO, I found that using the integrated terminal was quite productive as I didn't have to switch windows in between.

Looks minimal

When it comes to UI/UX, the word minimalism is often subjective but I guess Microsoft embraced the principles of minimalism for this editor.

Lots of extensions and great support

Thanks to the open source community, there are hundreds and thousands of extensions out there. By installing various extensions, you can make it your own editor and that part fascinates me.

Besides, it's developed by Microsoft, so it definitely has a strong support and community out there.

Conclusion

I guess the simplicity and flexibility of this editor is what made it more powerful amongst the developer community.

Now, I'm not going to say that it's a flawless editor, just like every other pieces of software, it does have it's cons. However, I decided to try out Visual Studio Code for a while and see how it goes for me.

If it doesn't then maybe I might write a post about why I didn't like using it, in the future.

Hope you liked reading this article.

Logging client IP addresses on Apache server

If you want to log the actual client IP address, you need to extract the X-Forward-For header from the request and in order to do that, you need to make a tiny edit in your httpd.conf file.

  1. Go to /etc/apache2/conf or /etc/httpd/conf and open httpd.conf file.
  2. Search for the string that starts with: LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined.
  3. Modify the %h to %{X-Forwarded-For}i. Now, it should look like this: LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined.
  4. Test the config to ensure that there are no typos by typing: apachectl configtest.
  5. Save and restart the service by typing: systemctl restart httpd or systemctl restart apache2.
  6. In your terminal, type tail -f /var/log/httpd/access.log and you'll be seeing the client IP being logged in your logs.

Hope you found this tip useful!