neuhalfen.name

A random collection of posts

Creating a Simple Solaris 11 IPS Package

Permalink

Besides several Linux VMs, I am using Solaris 11 for some of my home-tinkering systems. After an undetected three day outage of my mail server, I decided to install a monitoring solution for my systems.

Unwilling to go through the configure, make, make install triple jump every time I install a system, I set out to create a local IPS repository to serve my needs (and packages).

This tutorial consists of multiple steps

  1. Create and set up a local IPS in a dedicated zone
  2. Create a simple IPS package (this article).
  3. Create the Zabbix package and publish it.

Workflow / IPS vs. rpm/dpkg

The traditional workflow for package creation (e.g. rpm, and dpkg) is

  1. Get the pristine sources.
  2. Get all patches.
  3. Write a control file (RPM: specfile, Debian: controlfile) that describes:
    1. All build- and runtime dependencies,
    2. the order, in which the patches are to be applied to the pristine sources,
    3. the build script, and
    4. all files that are part of the final package.
    5. Optionally: Scripts to be executed at pre/post (de)installation time.
  4. A packaging tool (dpkg-deb / rpmbuild) takes the controlfile to patch and build the sources, and to …
  5. … assemble the package into a file.

Although dpkg and rpm differ, this process is not too far off to describe both systems.

In contrast to the file based systems, Solaris IPS works differently. Packages are assembled in a repository and not as a file on the local filesystem. Furthermore, there is not necessarily a controlfile. To worsen things, documentation, besides man pages (e.g. pkgsend, pkg), is rather sparse.

Two things are very different between rpm and IPS: Assembling an rpm-package includes a build step, where the source is compiled. This step is completely out of scope for IPS.
Another major difference is patching: rpm clearly separates between pristine source and patches. A classical rpm-controlfile references the pristine (upstream) sources and each to be applied patch explicitly. In contrast to that, IPS ignores only handles the build output.

Update: “pkgbuild”:“http://pkgbuild.sourceforge.net/” looks like a good candidate, to quote from the site:

pkgbuild is a tool for building Solaris SVr4 or IPS packages from RPM-like spec files. It is intended to be a plug-in replacement of the rpmbuild command. Most spec file elements are implemented plus a few more. The differences from real rpm spec files are summarised in the Manual It’s implemented as a set of perl scripts and modules.

Legend

I use several systems for package creation and -test. Each system is implemented as Solaris zone. Test systems are regularly reset to a known state by using zfs snapshots.

Systems used for package creation and test.

In listings the following prefixes are used when it is necessary to distinguish between systems:

  • host $ ... is the prefix used for the user root at the “global zone”.
  • ips $ ... denotes the user root inside the ips-zone.
  • ips-test $ ... denotes the user root inside the ips-test-zone.
  • client $ ... shows commands executed on clients (user root). Normally, this is a test client.
  • packager $ ... is the prefix used for the user jens, logged into the zone where the packages are prepared.

Prerequisites

This article assumes that you have a write enabled IPS repository running on your network. Mine is the publisher neuhalfen.name and is reacheable under http://ips.local.neuhalfen.name:80 . If you have not already done so, read how to create such a server here.

On the packaging machine

The packager is the machine that is used to create the packages.

First step: Make the repository known to the local IPS installation. This is not strictly necessary for package creation, but allows us to test package installation from packager.

packager ~ $ sudo pkg set-publisher -O http://ips.local.neuhalfen.name:80 neuhalfen.name

Create a program

In this article we will crate a very simple program and package it.

packager ~ $ mkdir test_package
packager ~ $ mkdir test_package/root
packager ~ $ cd test_package/
packager ~/test_package $ mkdir root/bin
packager ~/test_package$ cat <<EOF>root/bin/yes_sir
> #!/bin/sh
>
> echo YES
> EOF
packager ~/test_package $ cat <<EOF>root/bin/no_sir
> #!/bin/sh
>
> echo NO
> EOF
packager ~/test_package $ cat <<EOF>LICENSE
> Lorem ipsum dolor sit amet, ... sit amet."
> EOF
packager ~/test_package $ find .
.
./LICENSE
./root/bin
./root/bin/yes_sir
./root/bin/no_sir

Create the package

The program will be installed under /opt/test_package. For starters, we will do everything by hand.

packager ~/test_package $ export PKGSEND="pkgsend -s http://ips.local.neuhalfen.name:80"
packager ~/test_package $ eval `$PKGSEND open my_test_yesno@1.0-1`
packager ~/test_package $ $PKGSEND add license ./LICENSE license=lorem_ipsum
packager ~/test_package $ $PKGSEND add dir mode=0555 owner=root group=root path=/opt/yesno
packager ~/test_package $ $PKGSEND add file root/bin/yes_sir mode=0555 owner=root group=bin path=/opt/yesno/yes_sir
packager ~/test_package $ $PKGSEND add file root/bin/no_sir mode=0555 owner=root group=bin path=/opt/yesno/no_sir
packager ~/test_package $ $PKGSEND add depend fmri=pkg:/service/network/ssh type=require
packager ~/test_package $ $PKGSEND add set name=description value="Example Package"
packager ~/test_package $ $PKGSEND close
PUBLISHED
pkg://neuhalfen.name/my_test_yesno@1.0,5.11-1:20110704T233729Z

If you point your browser to your repository (http://ips.local.neuhalfen.name/ in my case), you will see the freshly created package.

Install the package

On the client, install the program:

client ~ $ pkg set-publisher -O http://ips.local.neuhalfen.name:80 neuhalfen.name
client ~ $ pkg install my_test_yesno
Packages to install: 1
Create boot environment: No
DOWNLOAD PKGS FILES XFER (MB)
Completed 1/1 3/3 0.0/0.0
PHASE ACTIONS
Install Phase 7/7
PHASE ITEMS
Package State Update Phase 1/1
Image State Update Phase 2/2
client ~ $ ls -la /opt/yesno
total 5
dr-xr-xr-x 2 root root 4 2011-07-05 01:51 .
drwxr-xr-x 5 root sys 5 2011-07-05 01:51 ..
-r-xr-xr-x 1 root root 19 2011-07-05 01:51 no_sir
-r-xr-xr-x 1 root root 20 2011-07-05 01:51 yes_sir

Create a newer version of the package

As time goes by, you update your package, and add a maybe command. To allow pkg update to work, we need to bump the version:

packager ~/test_package $ export PKGSEND="pkgsend -s http://ips.local.neuhalfen.name:80"
packager ~/test_package $ eval `$PKGSEND open my_test_yesno@1.1-1`
packager ~/test_package $ $PKGSEND add license ./LICENSE license=lorem_ipsum
packager ~/test_package $ $PKGSEND add dir mode=0555 owner=root group=bin path=/opt/yesno
packager ~/test_package $ $PKGSEND add file root/bin/yes_sir mode=0555 owner=root group=bin path=/opt/yesno/yes_sir
packager ~/test_package $ $PKGSEND add file root/bin/no_sir mode=0555 owner=root group=bin path=/opt/yesno/no_sir
packager ~/test_package $ $PKGSEND add file root/bin/maybe mode=0555 owner=root group=bin path=/opt/yesno/maybe
packager ~/test_package $ $PKGSEND add depend fmri=pkg:/service/network/ssh type=require
packager ~/test_package $ $PKGSEND add set name=description value="Example Package"
packager ~/test_package $ $PKGSEND close
PUBLISHED
pkg://neuhalfen.name/my_test_yesno@1.1,5.11-1:20110704T235557Z

The clients can now be updated by pkg update.

Automate the process

First install the GNU find-utils

packager ~ $ pkg install pkg:/file/gnu-findutils
packager ~ $ export ROOT=/opt/yesno
packager ~ $ export description="Description of the package"
packager ~ $ export user="root"
packager ~ $ export group="root"
packager ~ $ cd test_package
packager ~/test_package $ cat <<EOF>../send.sh
#!/bin/bash
cd $(pwd)
export PKGSEND="pkgsend -s http://ips.local.neuhalfen.name:80"
eval `$PKGSEND open my_test_yesno@1.1-1`
$PKGSEND add license ./LICENSE license=lorem_ipsum
$PKGSEND add depend fmri=pkg:/service/network/ssh type=require
$PKGSEND add set name=description value="${description}"
EOF
packager ~ $ # with current user/group find . -type d -not -name . -printf "\$PKGSEND add dir mode=%m owner=%u group=%g path=$ROOT/%h/%f \n" >> ../send.sh
packager ~ $ # with current user/group find . -type f -not -name LICENSE -printf "\$PKGSEND add file %h/%f mode=%m owner=%u group=%g path=$ROOT/%h/%f \n" >> ../send.sh
packager ~ $ find . -type d -not -name . -printf "\$PKGSEND add dir mode=%m owner=${user} group=${group} path=$ROOT/%h/%f \n" >> ../send.sh
packager ~ $ find . -type f -not -name LICENSE -printf "\$PKGSEND add file %h/%f mode=%m owner=${user} group=${group} path=$ROOT/%h/%f \n" >> ../send.sh
packager ~ $ find . -type l -not -name LICENSE -printf "\$PKGSEND add link path=%h/%f target=%l \n" >> ../send.sh
echo '$PKGSEND close' >> ./send.sh
sh send.sh
PUBLISHED
pkg://neuhalfen.name/my_test_yesno@1.1,5.11-1:20110724T210119Z
pkg info -r my_test_yesno
Name: my_test_yesno
Summary: Example Package
State: Not installed
Publisher: neuhalfen.name
Version: 1.1
Build Release: 5.11
Branch: 1
Packaging Date: Mon Jul 04 23:55:57 2011
Size: 656.00 B
FMRI: pkg://neuhalfen.name/my_test_yesno@1.1,5.11-1:20110704T235557Z
pkg contents -m -r my_test_yesno
set name=pkg.fmri value=pkg://neuhalfen.name/my_test_yesno@1.1,5.11-1:20110704T235557Z
license fbff24e5bc574b202c1896b017e0bda7b37a40eb chash=876e9ea0b84955e837c06963eb6267abf5d9abbc license=lorem_ipsum pkg.csize=211 pkg.size=593
set name=description value="Example Package"
depend fmri=pkg:/service/network/ssh type=require
dir group=bin mode=0555 owner=root path=opt/yesno
file 72599dceeea6747a1093dd717f7aa68e28f08a6a chash=07fde48d9194944f38fbd8fb610b88f83512e73b group=bin mode=0555 owner=root path=opt/yesno/yes_sir pkg.csize=45 pkg.size=25
file 88c1edc52f6c24ededc07625717cf4e1dbfa1b56 chash=d55699893029c0bcf68c8403ee2cca67bdd7f495 group=bin mode=0555 owner=root path=opt/yesno/no_sir pkg.csize=39 pkg.size=19
file 88c1edc52f6c24ededc07625717cf4e1dbfa1b56 chash=d55699893029c0bcf68c8403ee2cca67bdd7f495 group=bin mode=0555 owner=root path=opt/yesno/maybe pkg.csize=39 pkg.size=19

Using file-lists

This straight-forward approach might not always be what you want. Although it gives great flexibility, the number of $PKGSEND seems a bit over the top.

pkgsend has a command called include that takes a path to a specification file as parameter.

cd test_package
export ROOT=/opt/yesno
export description="Description of the package"
export user="root"
export group="root"
cat <<EOF >MANIFEST
license ./LICENSE license=lorem_ipsum
depend fmri=pkg:/service/network/ssh type=require
set name=description value="${description}"
EOF
cd root
find . -type d -not -name . -printf "dir mode=%m owner=${user} group=${group} path=$ROOT/%h/%f \n" >> ../MANIFEST
find . -type f -not -name LICENSE -and -not -name MANIFEST -printf "file %h/%f mode=%m owner=${user} group=${group} path=$ROOT/%h/%f \n" >> ../MANIFEST
find . -type l -not -name LICENSE -and -not -name MANIFEST -printf "link path=%h/%f target=%l \n" >> ../MANIFEST
cd ..
cat <<EOF>send.sh
#!/bin/bash
cd $(pwd)
export PKGSEND="pkgsend -s http://ips.local.neuhalfen.name:80"
eval \`\$PKGSEND open my_test_yesno@1.1-2\`
\$PKGSEND include MANIFEST
\$PKGSEND close
EOF

Importing the whole directory

Last, but not least, the import command: It imports an already existing directory. It does not allow relocation, that is in our case it would be necessary to first install our program in /opt/yesno and then import it.

More helpful is the generate command. It spits out a manifest for a directory.

pkgsend generate root
dir group=bin mode=0755 owner=root path=bin timestamp=20110724T215918Z
dir group=bin mode=0755 owner=root path=bin/x timestamp=20110705T001347Z
file bin/no_sir group=bin mode=0755 owner=root path=bin/no_sir pkg.size=19
file bin/yes_sir group=bin mode=0755 owner=root path=bin/yes_sir pkg.size=25
file bin/maybe group=bin mode=0755 owner=root path=bin/maybe pkg.size=22

Resources

  1. How To Copy an Oracle Solaris 11 Express Software Package Repository
  2. IPS Documentation
  3. Debian Binary Package Building HOWTO

Comments