Linux RPM Packaging
Original RPM guide is available here.
In this post, I'll be using CentOS 7 AWS instance.
$ cat /etc/*-release CentOS Linux release 7.6.1810 (Core) NAME="CentOS Linux" VERSION="7 (Core)" ID="centos" ID_LIKE="rhel fedora" VERSION_ID="7" PRETTY_NAME="CentOS Linux 7 (Core)" ANSI_COLOR="0;31" CPE_NAME="cpe:/o:centos:centos:7" HOME_URL="https://www.centos.org/" BUG_REPORT_URL="https://bugs.centos.org/" CENTOS_MANTISBT_PROJECT="CentOS-7" CENTOS_MANTISBT_PROJECT_VERSION="7" REDHAT_SUPPORT_PRODUCT="centos" REDHAT_SUPPORT_PRODUCT_VERSION="7" CentOS Linux release 7.6.1810 (Core) CentOS Linux release 7.6.1810 (Core)
An RPM package is simply a file containing other files and information about them needed by the system. Specifically, an RPM package consists of the cpio archive, which contains the files, and the RPM header, which contains metadata about the package. The rpm package manager uses this metadata to determine dependencies, where to install files, and other information.
There are two types of RPM packages:
- source RPM (SRPM)
- binary RPM
SRPMs and binary RPMs share the file format and tooling, but have different contents and serve different purposes. An SRPM contains source code, optionally patches to it, and a SPEC file, which describes how to build the source code into a binary RPM. A binary RPM contains the binaries built from the sources and patches.
We may want to install rpmdevtools:
$ sudo yum install rpmdevtools ... Installed: rpmdevtools.noarch 0:8.3-5.el7 ...
The rpmdevtools package, installed in Prerequisites, provides several utilities for packaging RPMs. To list these utilities, run:
$ rpm -ql rpmdevtools | grep bin /usr/bin/annotate-output /usr/bin/checkbashisms /usr/bin/licensecheck /usr/bin/manpage-alert /usr/bin/rpmdev-bumpspec /usr/bin/rpmdev-checksig /usr/bin/rpmdev-cksum /usr/bin/rpmdev-diff /usr/bin/rpmdev-extract /usr/bin/rpmdev-md5 /usr/bin/rpmdev-newinit /usr/bin/rpmdev-newspec /usr/bin/rpmdev-packager /usr/bin/rpmdev-rmdevelrpms /usr/bin/rpmdev-setuptree /usr/bin/rpmdev-sha1 /usr/bin/rpmdev-sha224 /usr/bin/rpmdev-sha256 /usr/bin/rpmdev-sha384 /usr/bin/rpmdev-sha512 /usr/bin/rpmdev-sort /usr/bin/rpmdev-sum /usr/bin/rpmdev-vercmp /usr/bin/rpmdev-wipetree /usr/bin/rpminfo /usr/bin/rpmls /usr/bin/spectool
The following packages need to installed as well:
$ sudo yum install gcc rpm-build rpm-devel rpmlint make python bash coreutils diffutils patch
The following is a complete, working RPM Spec file (hello-world.spec) with several things skipped and simplified from RPM PACKAGING GUIDE:
Name: hello-world Version: 1 Release: 1 Summary: Most simple RPM package License: FIXME %description This is my first RPM package, which does nothing. %prep # we have no source, so nothing here %build cat > hello-world.sh <<EOF #!/usr/bin/bash echo Hello world EOF %install mkdir -p %{buildroot}/usr/bin/ install -m 755 hello-world.sh %{buildroot}/usr/bin/hello-world.sh %files /usr/bin/hello-world.sh %changelog # let skip this for now
A SPEC file can be thought of as the "recipe" that the rpmbuild
utility uses to actually build an RPM. It tells the build system what to do by defining instructions in a series of sections.
$ rpmdev-setuptree
The command rpmdev-setuptree
creates several working directories. As those directories are stored permanently in $HOME, this command does not need to be used again.
$ tree ~/rpmbuild/ /home/centos/rpmbuild/ ├── BUILD ├── RPMS ├── SOURCES ├── SPECS └── SRPMS 5 directories, 0 files
The created directories serve these purposes:
Directory | Purpose |
BUILD |
When packages are built, various |
RPMS |
Binary RPMs are created here, in subdirectories for different architectures, for example in subdirectories |
SOURCES |
Here, the packager puts compressed source code archives and patches. The |
SPECS | The packager puts SPEC files here. |
SRPMS |
When |
$ rpmbuild -ba hello-world.spec Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.fn8RpG + umask 022 + cd /home/centos/rpmbuild/BUILD + exit 0 Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.tgmEqd + umask 022 + cd /home/centos/rpmbuild/BUILD + cat + exit 0 Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.alwLsK + umask 022 + cd /home/centos/rpmbuild/BUILD + '[' /home/centos/rpmbuild/BUILDROOT/hello-world-1-1.x86_64 '!=' / ']' + rm -rf /home/centos/rpmbuild/BUILDROOT/hello-world-1-1.x86_64 ++ dirname /home/centos/rpmbuild/BUILDROOT/hello-world-1-1.x86_64 + mkdir -p /home/centos/rpmbuild/BUILDROOT + mkdir /home/centos/rpmbuild/BUILDROOT/hello-world-1-1.x86_64 + mkdir -p /home/centos/rpmbuild/BUILDROOT/hello-world-1-1.x86_64/usr/bin/ + install -m 755 hello-world.sh /home/centos/rpmbuild/BUILDROOT/hello-world-1-1.x86_64/usr/bin/hello-world.sh + '[' '%{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-compress + /usr/lib/rpm/redhat/brp-strip /usr/bin/strip + /usr/lib/rpm/redhat/brp-strip-comment-note /usr/bin/strip /usr/bin/objdump + /usr/lib/rpm/redhat/brp-strip-static-archive /usr/bin/strip + /usr/lib/rpm/brp-python-bytecompile /usr/bin/python 1 + /usr/lib/rpm/redhat/brp-python-hardlink + /usr/lib/rpm/redhat/brp-java-repack-jars Processing files: hello-world-1-1.x86_64 Provides: hello-world = 1-1 hello-world(x86-64) = 1-1 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 /home/centos/rpmbuild/BUILDROOT/hello-world-1-1.x86_64 Wrote: /home/centos/rpmbuild/SRPMS/hello-world-1-1.src.rpm Wrote: /root/rpmbuild/RPMS/x86_64/hello-world-1-1.x86_64.rpm Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.ImEERm + umask 022 + cd /home/centos/rpmbuild/BUILD + /usr/bin/rm -rf /home/centos/rpmbuild/BUILDROOT/hello-world-1-1.x86_64 + exit 0
The command rpmbuild
creates the actual rpm package.
The file /root/rpmbuild/RPMS/x86_64/hello-world-1-1.x86_64.rpm is our first RPM package. It can be installed in the system and tested.
$ tree ~/rpmbuild/ /home/centos/rpmbuild/ ├── BUILD │ └── hello-world.sh ├── BUILDROOT ├── RPMS │ └── x86_64 │ └── hello-world-1-1.x86_64.rpm ├── SOURCES ├── SPECS └── SRPMS └── hello-world-1-1.src.rpm
In the context of RPM packaging, buildroot is a chroot environment. This means that the build artifacts are placed here using the same filesystem hierarchy as will be in the end user's system, with buildroot acting as the root directory. The placement of build artifacts should comply with the filesystem hierarchy standard of the end user’s system.
The files in buildroot are later put into a cpio archive, which becomes the main part of the RPM. When RPM is installed on the end user's system, these files are extracted in the root directory, preserving the correct hierarchy.
Syntax:
$ rpm --eval %{_MACRO}
Examples:
$ rpm --eval %{_bindir} /usr/bin $ rpm --eval %{_libexecdir} /usr/libexec $ rpm --eval %{?dist} .el7
A big part of packaging software into RPMs is editing the SPEC file. In this section we discuss how to create and modify a spec file.
To package new software, we need to create a new SPEC file. Instead of writing it manually from scratch, use the rpmdev-newspec utility. It creates an unpopulated SPEC file, and we fill in the necessary directives and fields.
In this post, we'll use the three example implementations of the 'Hello World!' program:
Let's place them in ~/rpmbuild/SOURCES.
$ scp -i ~/.ssh/my.pem *.gz centos@100.24.2.76:~/rpmbuild/SOURCES bello-0.1.tar.gz ... cello-1.0.tar.gz ... pello-0.1.1.tar.gz ... $ scp -i ~/.ssh/my.pem *.patch centos@100.24.2.76:~/rpmbuild/SOURCES cello-output-first-patch.patch ... $ tree ~/rpmbuild /home/centos/rpmbuild ├── BUILD ├── RPMS ├── SOURCES │ ├── bello-0.1.tar.gz │ ├── cello-1.0.tar.gz │ ├── cello-output-first-patch.patch │ └── pello-0.1.1.tar.gz ├── SPECS └── SRPMS 5 directories, 4 files
Let's create a SPEC file for each of the three programs:
$ cd ~/rpmbuild/SPECS $ rpmdev-newspec bello bello.spec created; type minimal, rpm version >= 4.11. $ rpmdev-newspec cello cello.spec created; type minimal, rpm version >= 4.11. $ rpmdev-newspec pello pello.spec created; type minimal, rpm version >= 4.11.
The ~/rpmbuild/SPECS/ directory now contains three SPEC files named bello.spec, cello.spec, and pello.spec.
$ tree ~/rpmbuild /home/centos/rpmbuild ├── BUILD ├── RPMS ├── SOURCES │ ├── bello-0.1.tar.gz │ ├── cello-1.0.tar.gz │ ├── cello-output-first-patch.patch │ └── pello-0.1.1.tar.gz ├── SPECS │ ├── bello.spec │ ├── cello.spec │ └── pello.spec └── SRPMS 5 directories, 7 files
~/rpmbuild/SPECS/bello.spec:
Name: bello Version: 0.1 Release: 1%{?dist} Summary: Hello World example implemented in bash script License: GPLv3+ URL: https://www.example.com/%{name} Source0: https://www.example.com/%{name}/releases/%{name}-%{version}.tar.gz Requires: bash BuildArch: noarch %description The long-tail description for our Hello World Example implemented in bash script. %prep %setup -q %build %install mkdir -p %{buildroot}/%{_bindir} install -m 0755 %{name} %{buildroot}/%{_bindir}/%{name} %files %license LICENSE %{_bindir}/%{name} %changelog * Tue May 31 2016 Adam Miller- 0.1-1 - First bello package - Example second item in the changelog for version-release 0.1-1
~/rpmbuild/SPECS/pello.spec:
Name: pello Version: 0.1.1 Release: 1%{?dist} Summary: Hello World example implemented in python License: GPLv3+ URL: https://www.example.com/%{name} Source0: https://www.example.com/%{name}/releases/%{name}-%{version}.tar.gz BuildRequires: python Requires: python Requires: bash BuildArch: noarch %description The long-tail description for our Hello World Example implemented in Python. %prep %setup -q %build python -m compileall %{name}.py %install mkdir -p %{buildroot}/%{_bindir} mkdir -p %{buildroot}/usr/lib/%{name} cat > %{buildroot}/%{_bindir}/%{name} <<-EOF #!/bin/bash /usr/bin/python /usr/lib/%{name}/%{name}.pyc EOF chmod 0755 %{buildroot}/%{_bindir}/%{name} install -m 0644 %{name}.py* %{buildroot}/usr/lib/%{name}/ %files %license LICENSE %dir /usr/lib/%{name}/ %{_bindir}/%{name} /usr/lib/%{name}/%{name}.py* %changelog * Tue May 31 2016 Adam Miller- 0.1.1-1 - First pello package
~/rpmbuild/SPECS/cello.spec:
Name: cello Version: 1.0 Release: 1%{?dist} Summary: Hello World example implemented in C License: GPLv3+ URL: https://www.example.com/%{name} Source0: https://www.example.com/%{name}/releases/%{name}-%{version}.tar.gz Patch0: cello-output-first-patch.patch BuildRequires: gcc BuildRequires: make %description The long-tail description for our Hello World Example implemented in C. %prep %setup -q %patch0 %build make %{?_smp_mflags} %install %make_install %files %license LICENSE %{_bindir}/%{name} %changelog * Tue May 31 2016 Adam Miller- 1.0-1 - First cello package
RPMs are built with the rpmbuild
command. Different scenarios and desired outcomes require different combinations of arguments to rpmbuild
. This section describes the two prime scenarios:
- building a source RPM
- building a binary RPM
The rpmbuild
command expects a certain directory and file structure. This is the same structure as set up by the rpmdev-setuptree utility. The previous instructions also confirmed to the required structure.
Why build a Source RPM (SRPM)?
- To preserve the exact source of a certain Name-Version-Release of the RPM that was deployed to an environment. This includes the exact SPEC file, the source code, and all relevant patches. This is useful for looking back in history and for debugging.
- To be able to build a binary RPM on a different hardware platform or architecture.
To create a SRPM, use the following command (Substitute SPECFILE with the SPEC file. The -bs option stands for "build source"):
$ rpmbuild -bs _SPECFILE_
$ cd ~/rpmbuild/SPECS/ $ rpmbuild -bs bello.spec Wrote: /home/centos/rpmbuild/SRPMS/bello-0.1-1.el7.src.rpm $ rpmbuild -bs pello.spec Wrote: /home/centos/rpmbuild/SRPMS/pello-0.1.1-1.el7.src.rpm $ rpmbuild -bs cello.spec Wrote: /home/centos/rpmbuild/SRPMS/cello-1.0-1.el7.src.rpm
$ tree ~/rpmbuild /home/centos/rpmbuild ├── BUILD ├── BUILDROOT ├── RPMS ├── SOURCES │ ├── bello-0.1.tar.gz │ ├── cello-1.0.tar.gz │ ├── cello-output-first-patch.patch │ └── pello-0.1.1.tar.gz ├── SPECS │ ├── bello.spec │ ├── cello.spec │ └── pello.spec └── SRPMS ├── bello-0.1-1.el7.src.rpm ├── cello-1.0-1.el7.src.rpm └── pello-0.1.1-1.el7.src.rpm 6 directories, 10 files
There are two methods for building Binary RPMs:
- Rebuilding it from a SRPM using the
rpmbuild --rebuild
command. - Building it from a SPEC file using the
rpmbuild -bb
command. The -bb option stands for "build binary".
To rebuild bello, pello, and cello from Source RPMs (SRPMs), run:
$ rpmbuild --rebuild ~/rpmbuild/SRPMS/bello-0.1-1.el7.src.rpm Installing /home/centos/rpmbuild/SRPMS/bello-0.1-1.el7.src.rpm ... $ rpmbuild --rebuild ~/rpmbuild/SRPMS/pello-0.1.1-1.el7.src.rpm Installing /home/centos/rpmbuild/SRPMS/pello-0.1.1-1.el7.src.rpm ... $ rpmbuild --rebuild ~/rpmbuild/SRPMS/cello-1.0-1.el7.src.rpm Installing /home/centos/rpmbuild/SRPMS/cello-1.0-1.el7.src.rpm ...
The resulting binary RPMs are in ~/rpmbuild/RPMS/YOURARCH where YOURARCH is your architecture or in ~/rpmbuild/RPMS/noarch/, if the package is not architecture-specific.
$ tree ~/rpmbuild /home/centos/rpmbuild ├── BUILD ├── BUILDROOT ├── RPMS │ ├── noarch │ │ ├── bello-0.1-1.el7.noarch.rpm │ │ └── pello-0.1.1-1.el7.noarch.rpm │ └── x86_64 │ ├── cello-1.0-1.el7.x86_64.rpm │ └── cello-debuginfo-1.0-1.el7.x86_64.rpm ├── SOURCES ├── SPECS └── SRPMS ├── bello-0.1-1.el7.src.rpm ├── cello-1.0-1.el7.src.rpm └── pello-0.1.1-1.el7.src.rpm 8 directories, 7 files
Note that invoking rpmbuild --rebuild
involves:
- Installing the contents of the SRPM - the SPEC file and the source code - into the ~/rpmbuild/ directory.
- Building using the installed contents.
- Removing the SPEC file and the source code.
We can retain the SPEC file and the source code after building. For this, we have two options:
- When building, use the --recompile option instead of --rebuild.
-
Install the SRPMs using these commands:
$ rpm -Uvh ~/rpmbuild/SRPMS/bello-0.1-1.el7.src.rpm Updating / installing... 1:bello-0.1-1.el7 ################################# [100%] $ rpm -Uvh ~/rpmbuild/SRPMS/pello-0.1.1-1.el7.src.rpm Updating / installing... 1:pello-0.1.1-1.el7 ################################# [100%] $ rpm -Uvh ~/rpmbuild/SRPMS/cello-1.0-1.el7.src.rpm Updating / installing... 1:cello-1.0-1.el7 ################################# [100%]
Note that we execute the
rpm -Uvh
commands above to continue interacting with the SPEC files and sources.
$ rpmbuild --recompile ~/rpmbuild/SRPMS/bello-0.1-1.el7.src.rpm Installing /home/centos/rpmbuild/SRPMS/bello-0.1-1.el7.src.rpm ... $ rpmbuild --recompile ~/rpmbuild/SRPMS/pello-0.1.1-1.el7.src.rpm Installing /home/centos/rpmbuild/SRPMS/pello-0.1.1-1.el7.src.rpm ... $ rpmbuild --recompile ~/rpmbuild/SRPMS/cello-1.0-1.el7.src.rpm Installing /home/centos/rpmbuild/SRPMS/cello-1.0-1.el7.src.rpm
$ tree ~/rpmbuild /home/centos/rpmbuild ├── BUILD │ ├── bello-0.1 │ │ ├── bello │ │ ├── debugfiles.list │ │ ├── debuglinks.list │ │ ├── debugsources.list │ │ ├── elfbins.list │ │ └── LICENSE │ ├── cello-1.0 │ │ ├── cello │ │ ├── cello.c │ │ ├── debugfiles.list │ │ ├── debuglinks.list │ │ ├── debugsources.list │ │ ├── elfbins.list │ │ ├── LICENSE │ │ └── Makefile │ └── pello-0.1.1 │ ├── debugfiles.list │ ├── debuglinks.list │ ├── debugsources.list │ ├── elfbins.list │ ├── LICENSE │ ├── pello.py │ └── pello.pyc ├── BUILDROOT │ ├── bello-0.1-1.el7.x86_64 │ │ └── usr │ │ ├── bin │ │ │ └── bello │ │ └── share │ │ └── licenses │ │ └── bello-0.1 │ │ └── LICENSE │ ├── cello-1.0-1.el7.x86_64 │ │ └── usr │ │ ├── bin │ │ │ └── cello │ │ ├── lib │ │ │ └── debug │ │ │ └── usr │ │ │ └── bin │ │ │ └── cello.debug │ │ ├── share │ │ │ └── licenses │ │ │ └── cello-1.0 │ │ │ └── LICENSE │ │ └── src │ │ └── debug │ │ └── cello-1.0 │ │ └── cello.c │ └── pello-0.1.1-1.el7.x86_64 │ └── usr │ ├── bin │ │ └── pello │ ├── lib │ │ └── pello │ │ ├── pello.py │ │ ├── pello.pyc │ │ └── pello.pyo │ └── share │ └── licenses │ └── pello-0.1.1 │ └── LICENSE ├── RPMS │ ├── noarch │ │ ├── bello-0.1-1.el7.noarch.rpm │ │ └── pello-0.1.1-1.el7.noarch.rpm │ └── x86_64 │ ├── cello-1.0-1.el7.x86_64.rpm │ └── cello-debuginfo-1.0-1.el7.x86_64.rpm ├── SOURCES │ ├── bello-0.1.tar.gz │ ├── cello-1.0.tar.gz │ ├── cello-output-first-patch.patch │ └── pello-0.1.1.tar.gz ├── SPECS │ ├── bello.spec │ ├── cello.spec │ └── pello.spec └── SRPMS ├── bello-0.1-1.el7.src.rpm ├── cello-1.0-1.el7.src.rpm └── pello-0.1.1-1.el7.src.rpm 38 directories, 46 files
To build bello, pello, and cello from their SPEC files, run the followings:
$ rpmbuild -bb ~/rpmbuild/SPECS/bello.spec $ rpmbuild -bb ~/rpmbuild/SPECS/pello.spec $ rpmbuild -bb ~/rpmbuild/SPECS/cello.spec
Now we have built RPMs from SPEC files.
$ tree ~/rpmbuild /home/centos/rpmbuild ├── BUILD │ ├── bello-0.1 │ │ ├── bello │ │ ├── debugfiles.list │ │ ├── debuglinks.list │ │ ├── debugsources.list │ │ ├── elfbins.list │ │ └── LICENSE │ ├── cello-1.0 │ │ ├── cello │ │ ├── cello.c │ │ ├── debugfiles.list │ │ ├── debuglinks.list │ │ ├── debugsources.list │ │ ├── elfbins.list │ │ ├── LICENSE │ │ └── Makefile │ └── pello-0.1.1 │ ├── debugfiles.list │ ├── debuglinks.list │ ├── debugsources.list │ ├── elfbins.list │ ├── LICENSE │ ├── pello.py │ └── pello.pyc ├── BUILDROOT ├── RPMS │ ├── noarch │ │ ├── bello-0.1-1.el7.noarch.rpm │ │ └── pello-0.1.1-1.el7.noarch.rpm │ └── x86_64 │ ├── cello-1.0-1.el7.x86_64.rpm │ └── cello-debuginfo-1.0-1.el7.x86_64.rpm ├── SOURCES │ ├── bello-0.1.tar.gz │ ├── cello-1.0.tar.gz │ ├── cello-output-first-patch.patch │ └── pello-0.1.1.tar.gz ├── SPECS │ ├── bello.spec │ ├── cello.spec │ └── pello.spec └── SRPMS ├── bello-0.1-1.el7.src.rpm ├── cello-1.0-1.el7.src.rpm └── pello-0.1.1-1.el7.src.rpm 11 directories, 35 files
Linux - system, cmds & shell
- Linux Tips - links, vmstats, rsync
- Linux Tips 2 - ctrl a, curl r, tail -f, umask
- Linux - bash I
- Linux - bash II
- Linux - Uncompressing 7z file
- Linux - sed I (substitution: sed 's///', sed -i)
- Linux - sed II (file spacing, numbering, text conversion and substitution)
- Linux - sed III (selective printing of certain lines, selective definition of certain lines)
- Linux - 7 File types : Regular, Directory, Block file, Character device file, Pipe file, Symbolic link file, and Socket file
- Linux shell programming - introduction
- Linux shell programming - variables and functions (readonly, unset, and functions)
- Linux shell programming - special shell variables
- Linux shell programming : arrays - three different ways of declaring arrays & looping with $*/$@
- Linux shell programming : operations on array
- Linux shell programming : variables & commands substitution
- Linux shell programming : metacharacters & quotes
- Linux shell programming : input/output redirection & here document
- Linux shell programming : loop control - for, while, break, and break n
- Linux shell programming : string
- Linux shell programming : for-loop
- Linux shell programming : if/elif/else/fi
- Linux shell programming : Test
- Managing User Account - useradd, usermod, and userdel
- Linux Secure Shell (SSH) I : key generation, private key and public key
- Linux Secure Shell (SSH) II : ssh-agent & scp
- Linux Secure Shell (SSH) III : SSH Tunnel as Proxy - Dynamic Port Forwarding (SOCKS Proxy)
- Linux Secure Shell (SSH) IV : Local port forwarding (outgoing ssh tunnel)
- Linux Secure Shell (SSH) V : Reverse SSH Tunnel (remote port forwarding / incoming ssh tunnel) /)
- Linux Processes and Signals
- Linux Drivers 1
- tcpdump
- Linux Debugging using gdb
- Embedded Systems Programming I - Introduction
- Embedded Systems Programming II - gcc ARM Toolchain and Simple Code on Ubuntu/Fedora
- LXC (Linux Container) Install and Run
- Linux IPTables
- Hadoop - 1. Setting up on Ubuntu for Single-Node Cluster
- Hadoop - 2. Runing on Ubuntu for Single-Node Cluster
- ownCloud 7 install
- Ubuntu 14.04 guest on Mac OSX host using VirtualBox I
- Ubuntu 14.04 guest on Mac OSX host using VirtualBox II
- Windows 8 guest on Mac OSX host using VirtualBox I
- Ubuntu Package Management System (apt-get vs dpkg)
- RPM Packaging
- How to Make a Self-Signed SSL Certificate
- Linux Q & A
- DevOps / Sys Admin questions
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization