Building RPM Packages in RHEL

Mohan JuriyaniMohan Juriyani
8 min read

๐Ÿ‘‹ Hello Guys,

Have you ever wondered how the commands we use in Linux are actually created?

If yes, then you're on the right page โ€” and Iโ€™ve got your back! ๐Ÿ™Œ
In this post, Iโ€™ll walk you through the process of creating your own Linux command โ€” one that can even surpass existing ones or fulfill a completely new purpose you envision.

So, without further ado, let's jump into the topic and start building our own command-line tool!

Prerequisite:

  • A Linux VM with active RedHat Subscription.

  • knowledge of any scripting language like bash, go or python..etc.

So yeah that was the initial requirement for building a rpm

CTOP :

let me introduce with the ctop command, this command I have created with the bash script in a simple manner to explain it more efficiently with some basic features and in interactive way.

explanation of output:

  1. Tells hostname, uptime, load avg and Idle percentage. [ PS: it will also tell if your cpu goes high or not so no need to calculate the idle state every time ; ) ]

  2. showing utilization of critical fs, memory usage and obviously the CPU processes.

  3. Get refreshed in every 2 second.

Below is the output of command:

Lets start with the technical steps :

Install the rpm dev tools packages to begin with the creation.

dnf install rpm-build rpmdevtools -y

once installation is done create a subtree from where rpm will read the content of the rpm binary that we are tying to create using below command.

[root@awx ~]# rpmdev-setuptree
[root@awx ~]# ls -lrth rpmbuild/
total 0
drwxr-xr-x. 3 root root  20 May 10 19:27 RPMS
drwxr-xr-x. 4 root root  36 May 10 19:49 BUILD
drwxr-xr-x. 2 root root  42 May 10 21:10 SOURCES
drwxr-xr-x. 2 root root  59 May 10 21:11 SPECS
drwxr-xr-x. 2 root root 128 May 10 21:11 SRPMS
drwxr-xr-x. 2 root root   6 May 10 21:11 BUILDROOT
[root@awx ~]#

Explanation of the each directory with details under the rpmbuild workspace

DirectoryPurposeUsed DuringCleanupNotes
BUILD/Temporary workspace for unpacking & building%prep, %buildYesWhere the source code is extracted and compiled.
BUILDROOT/Fake root directory for installation%installYesFiles are staged here before packaging. Use %{buildroot} macro.
RPMS/Final binary RPM packages (.rpm)Post %installNoOutput location of installable packages.
SRPMS/Source RPMs (.src.rpm)After %prepNoContains spec + sources for rebuilding RPMs elsewhere.
SOURCES/Tarballs, patches, and helper files%prepNoInput files referenced in the spec file (Source0, Patch0, etc.).
SPECS/Spec files with build instructionsStart of buildNoMain build script (.spec) that drives the RPM build process.

There are several ways to create packages โ€” or more specifically, package your scripts โ€” in Linux.

For example, in this guide, I used a single Bash script to create the ctop command. But if you want to include multiple files (like configuration files, helper scripts, etc.), it's a good idea to:

  1. Create a dedicated directory.

  2. Place all your required files inside that directory.

  3. Bundle them into a tarball (.tar.gz) โ€” this makes it easier to manage and distribute as a single source archive.

As mentioned earlier, RPM builds expect sources to be placed in the ~/rpmbuild/SOURCES/ directory.

You can either:

  • Create your script manually using an editor like vi or nano, or

  • Place your tarball there if you're bundling multiple files.

For our current scenario (a simple CPU monitoring script), we will:

  • Keep the ctop script inside the ~/rpmbuild/SOURCES/ directory.

  • Continue with the RPM creation from there.

[root@awx ~]# cat rpmbuild/SOURCES/ctop
############################################################################
################## AUTHOR : Mohan Juriyani #################################
############################################################################
#!/bin/bash #SHEBANG chars#####

REFRESH=2  ##########timeout of refreshment of the command
CPU_THRESHOLD=80  # % usage above which CPU is considered high

while true; do
    clear

    echo -e "\033[1;34m======== ctop โ€“ A new substitue for top ========\033[0m"
    echo -e "\033[1;32mHostname: $(hostname)\033[0m"
    echo -e "\033[1;33mUptime: $(uptime -p)\033[0m"

    CPU_IDLE=$(top -bn1 | grep "Cpu(s)" | awk -F',' '{ print $4 }' | awk '{print $1}') ##pulling out the IDLE cpu percentage##
    CPU_USED=$(echo "100 - $CPU_IDLE" | bc)
    echo -e "\033[1;36mCPU Load: $(uptime | awk -F'load average:' '{ print $2 }')\033[0m"
    echo -e "\033[1;36mCPU Usage: $CPU_USED% (Idle: $CPU_IDLE%)\033[0m"

    CPU_ALERT=$(echo "$CPU_USED > $CPU_THRESHOLD" | bc)
    if [ "$CPU_ALERT" -eq 1 ]; then
        echo -e "\033[1;41mWARNING: High CPU Usage!\033[0m"

      else
        echo -e "\033[1;41mCPU usage is normal !!\033[0m"
    fi

    echo -e "\n\033[1;36mMemory Usage:\033[0m"
    free -h | grep -E "Mem|Swap"

    echo -e "\n\033[1;36mDisk Usage:\033[0m"
    df -h --output=source,size,used,avail,pcent,target | grep -v tmpfs

    echo -e "\n\033[1;31mTop 5 CPU-consuming processes:\033[0m"
    ps -eo pid,comm,%cpu,%mem --sort=-%cpu | head -n 6

    echo -e "\n\033[1;34mTop 5 Memory-consuming processes:\033[0m"
    ps -eo pid,comm,%cpu,%mem --sort=-%mem | head -n 6

    echo -e "\033[1;34m============================================\033[0m"
    sleep $REFRESH
done

once this is done switch to SPEC directory to create a specification of file.

There are two ways you can create it using VI editor or by using command any way if you use command you still have to edit it.

[root@awx ~]# cd ~/rpmbuild/SPECS/
[root@awx ~]# rpmdev-newspec ctop

#you will see a file like below.

[root@awx SPECS]# ls -lrth
total 12K
-rw-r--r--. 1 root root 760 May 10 21:11 ctop.spec
[root@awx SPECS]#

Once you see the spec file go ahead and edit it by mentioning the below things.

Name:           ctop
Version:        1.1
Release:        1%{?dist}
Summary:        Bash-based system resource monitor like top

License:        MIT
URL:            https://example.com/ctop
Source0:        ctop

BuildArch:      noarch
Requires:       bash, coreutils, procps-ng, bc

%description
ctop is a simple Bash-based system resource monitoring tool that shows CPU, memory,
disk usage, and top processes, including CPU idle percentage and alerts for high CPU usage.

%prep
# Nothing to prep since this is just a script

%build
# No build step needed

%install
mkdir -p %{buildroot}/usr/local/bin
install -m 0755 %{SOURCE0} %{buildroot}/usr/local/bin/ctop

%files
/usr/local/bin/ctop

%changelog
* Sat May 10 2025 Mohan Juriyani - 1.0-1
- Initial release

๐Ÿ“„ RPM Spec File Breakdown โ€“ ctop.spec

SectionPurposeExample / Notes
NameName of the packagectop
VersionVersion of the software1.1
ReleaseRelease number, optionally includes distro macro1%{?dist} โ†’ becomes 1.el8, etc.
SummaryShort description of the packageShown in dnf info
LicenseLicense under which the script is distributedMIT
URLHomepage or documentation linkhttps://example.com/ctop
Source0Main source file to be packagedName of the script: ctop
BuildArchTarget architecture (e.g., noarch for scripts)Not architecture-specific
RequiresRuntime dependencies for the script to functionbash, coreutils, procps-ng, bc
%descriptionDetailed description shown in package managersExplains what ctop does
%prepUnpacks/sets up source code โ€“ optional for scriptsSkipped here, as no tarball used
%buildCompilation step โ€“ not needed for scriptsCommented out
%installInstalls script to a staged directory (%{buildroot})Uses install -m 0755 to place the script in /usr/local/bin/
%filesFiles included in the final RPMLists paths like /usr/local/bin/ctop
%changelogHistory of changes/releasesGood practice to update with every release

Once you are done with writing the spec file its time to build it.

use below command to do so.

[root@awx SPECS]# rpmbuild -ba ctop.spec
setting SOURCE_DATE_EPOCH=1746835200
Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.RiHbkh
+ umask 022
+ cd /root/rpmbuild/BUILD
+ RPM_EC=0
++ jobs -p
+ exit 0
Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.Df0IRX
+ umask 022
+ cd /root/rpmbuild/BUILD
+ RPM_EC=0
++ jobs -p
+ exit 0
Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.a9wevN
+ umask 022
+ cd /root/rpmbuild/BUILD
+ '[' /root/rpmbuild/BUILDROOT/ctop-1.1-1.el9.x86_64 '!=' / ']'
+ rm -rf /root/rpmbuild/BUILDROOT/ctop-1.1-1.el9.x86_64
++ dirname /root/rpmbuild/BUILDROOT/ctop-1.1-1.el9.x86_64
+ mkdir -p /root/rpmbuild/BUILDROOT
+ mkdir /root/rpmbuild/BUILDROOT/ctop-1.1-1.el9.x86_64
+ mkdir -p /root/rpmbuild/BUILDROOT/ctop-1.1-1.el9.x86_64/usr/local/bin
+ install -m 0755 /root/rpmbuild/SOURCES/ctop /root/rpmbuild/BUILDROOT/ctop-1.1-1.el9.x86_64/usr/local/bin/ctop
+ '[' '%{buildarch}' = noarch ']'
+ QA_CHECK_RPATHS=1
+ case "${QA_CHECK_RPATHS:-}" in
+ /usr/lib/rpm/check-rpaths
+ /usr/lib/rpm/check-buildroot
+ /usr/lib/rpm/redhat/brp-ldconfig
+ /usr/lib/rpm/brp-compress
+ /usr/lib/rpm/brp-strip /usr/bin/strip
+ /usr/lib/rpm/brp-strip-comment-note /usr/bin/strip /usr/bin/objdump
+ /usr/lib/rpm/redhat/brp-strip-lto /usr/bin/strip
+ /usr/lib/rpm/brp-strip-static-archive /usr/bin/strip
+ /usr/lib/rpm/redhat/brp-python-bytecompile '' 1 0
+ /usr/lib/rpm/brp-python-hardlink
+ /usr/lib/rpm/redhat/brp-mangle-shebangs
mangling shebang in /usr/local/bin/ctop from /bin/bash to #!/usr/bin/bash
Processing files: ctop-1.1-1.el9.noarch
Provides: ctop = 1.1-1.el9
Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
Requires: /usr/bin/bash
Checking for unpackaged file(s): /usr/lib/rpm/check-files /root/rpmbuild/BUILDROOT/ctop-1.1-1.el9.x86_64
Wrote: /root/rpmbuild/SRPMS/ctop-1.1-1.el9.src.rpm
Wrote: /root/rpmbuild/RPMS/noarch/ctop-1.1-1.el9.noarch.rpm
Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.Dnt4Ag
+ umask 022
+ cd /root/rpmbuild/BUILD
+ /usr/bin/rm -rf /root/rpmbuild/BUILDROOT/ctop-1.1-1.el9.x86_64
+ RPM_EC=0
++ jobs -p
+ exit 0
[root@awx SPECS]#

if there is any resolve it before proceeding ahead with the other step

Once your build is successfully completed you will find the rpm file inside RPMS directory so lets switch to that and install the command.

[root@awx noarch]# pwd
/root/rpmbuild/RPMS/noarch
[root@awx noarch]# ls -lrth
total 32K
-rw-r--r--. 1 root root 7.5K May 10 22:52 ctop-1.1-1.el9.noarch.rpm
[root@awx noarch]# dnf install ctop-1.1-1.el9.noarch.rpm

DONE.

Now after completing all the steps we have successfully create a rpm packages you can use the command to view your CPU and memory usage.

[root@awx noarch]# ctop
======== ctop โ€“ A new substitue for top ========
Hostname: awx
Uptime: up 4 hours
CPU Load:  0.03, 0.03, 0.00
CPU Usage: 6.2% (Idle: 93.8%)
CPU usage is normal !!

Memory Usage:
Mem:           1.7Gi       871Mi       388Mi       1.0Mi       678Mi       902Mi
Swap:          2.0Gi        62Mi       1.9Gi

Disk Usage:
Filesystem             Size  Used Avail Use% Mounted on
/dev/mapper/rhel-root   19G   14G  5.9G  70% /
/dev/sda1             1014M  285M  730M  29% /boot

Top 5 CPU-consuming processes:
    PID COMMAND         %CPU %MEM
   5042 java             0.6 22.5
  64824 kworker/0:2-cgr  0.3  0.0
  63968 kworker/0:3-eve  0.1  0.0
      1 systemd          0.0  0.9
      2 kthreadd         0.0  0.0

Top 5 Memory-consuming processes:
    PID COMMAND         %CPU %MEM
   5042 java             0.6 22.5
    814 containerd       0.0  2.7
   1224 dockerd          0.0  2.5
  48492 python3          0.0  2.0
    786 tuned            0.0  1.5
============================================

Thanks for bearing with me till here !

Hope you have learn something new.

Key- takeaways :

  1. Workspace directory should have correct permission and you should have sudo privileges.

  2. Incase if your command or binary or rpm having different files make sure you create tarball and then create spec file and all.

10
Subscribe to my newsletter

Read articles from Mohan Juriyani directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Mohan Juriyani
Mohan Juriyani