repo

In order to have your clients install puppet at boot time we need to configure a yum repository for our puppet packages. I have previously posted how to do this as a blog entry here. There are x steps in creating a repo:

making your own repo, a comps.xml a group, using yum groupinstall make your own packages. sign them, key signing. about making packages in an appendix using yum, automatic for workstations, you control the repos.

signing user

The first step in creating your own repository is to make a gpg key for your key signer. I find it best to separate out the key signing completely from myself and have a separate account for signing rpms.
[root@server0 ~]# useradd signer
[root@server0 ~]# passwd signer
Changing password for user signer.
New UNIX password: 
Retype new UNIX password: 
passwd: all authentication tokens updated successfully.
[root@server0 ~]# su - signer
[signer@server0 ~]$ gpg --gen-key
gpg (GnuPG) 1.4.5; Copyright (C) 2006 Free Software Foundation, Inc.
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions. See the file COPYING for details.

Please select what kind of key you want:
   (1) DSA and Elgamal (default)
   (2) DSA (sign only)
   (5) RSA (sign only)
Your selection? 5
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 
Requested keysize is 2048 bits
Please specify how long the key should be valid.
         0 = key does not expire
        = key expires in n days
      w = key expires in n weeks
      m = key expires in n months
      y = key expires in n years
Key is valid for? (0) 10y
Key expires at Sun 21 Jul 2019 04:32:53 PM EDT
Is this correct? (y/N) y

You need a user ID to identify your key; the software constructs the user ID
from the Real Name, Comment and Email Address in this form:
    "Heinrich Heine (Der Dichter) "

Real name: Repository Signer
Email address: signer@example.com
Comment: Example Com
You selected this USER-ID:
    "Repository Signer (Example Com) "

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
You need a Passphrase to protect your secret key.

We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.

...+++++
..........+++++
gpg: key 44CB93FD marked as ultimately trusted
public and secret key created and signed.

gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: next trustdb check due at 2019-07-21
pub   2048R/44CB93FD 2009-07-23 [expires: 2019-07-21]
      Key fingerprint = BD7F E3E1 3403 3F35 5DA7  C8AF CC08 B6BA 44CB 93FD
uid                  Repository Signer (Example Com) 

Note that this key cannot be used for encryption.  You may want to use
the command "--edit-key" to generate a subkey for this purpose.
Next, export the newly created key to a file.
[signer@server0 ~]$ gpg --export -a "Repository Signer (Example Com) " >RPM-GPG-KEY-example.com
Now, import the key into the rpm database to verify everything is working correctly.
[root@server0 install]# rpm -q gpg-pubkey-*
gpg-pubkey-f51839ac-46362566
gpg-pubkey-b2980b13-3c1d0597
[root@server0 install]# rpm --import ~signer/RPM-GPG-KEY-example.com 
[root@server0 install]# rpm -q gpg-pubkey-*
gpg-pubkey-f51839ac-46362566
gpg-pubkey-b2980b13-3c1d0597
gpg-pubkey-44cb93fd-4a68c9c4

signing rpms

RPMs Needed

This is a list of the packages needed
puppet
puppet-server
facter
ruby-shadow
augeas
augeas-libs
ruby-augeas
func
certmaster
smolt
python-ctypes
python-paste
python-simplejson

Now, you'll need to either download the src rpm or binary rpms for the packages you wish to redistribute to your clients (Make sure you can legally redistribute these rpms). For our purposes we will need the func, puppet, facter and augeas rpms. We will download them from the epel repository. Using lftp is one of the quickest ways to find all the right files and download them. First we'll setup the directories for our repository, then we'll start downloading.

[root@server0 ~]# cd /var/www/html/install
[root@server0 install]# mkdir -p Local/SRPMS Local/i386 Local/x86_64
[root@server0 install]# cd Local
[root@server0 Local]# lftp http://download.fedora.redhat.com/pub/epel/5/x86_64/
cd ok, cwd=/pub/epel/5/x86_64                                              
lftp download.fedora.redhat.com:/pub/epel/5/x86_64> get puppet-0.24.8-1.el5.1.noarch.rpm 
554952 bytes transferred                                             
lftp download.fedora.redhat.com:/pub/epel/5/x86_64> get puppet-server-0.24.8-1.el5.1.noarch.rpm 
26918 bytes transferred                                                   
lftp download.fedora.redhat.com:/pub/epel/5/x86_64> get func-0.24-1.el5.noarch.rpm 
282228 bytes transferred                                     
...
lftp download.fedora.redhat.com:/pub/epel/5/x86_64> quit
[root@server0 Local]# ls
augeas-0.5.1-1.el5.x86_64.rpm       puppet-server-0.24.8-1.el5.1.noarch.rpm
augeas-libs-0.5.1-1.el5.x86_64.rpm  ruby-augeas-0.2.0-1.el5.x86_64.rpm
...
python-simplejson-2.0.3-2.el5.x86_64.rpm

At this point we have our rpms downloaded, we need to place them in the appropriate directories, and also sign them (they are signed by the epel repo at this point). We'll sign them first, this requires giving the signer user permission to write in the Local directory and setting the .rpmmacros file in signers home directory to use the correct signing key.

[root@server0 Local]# rpm -K augeas-0.5.1-1.el5.x86_64.rpm 
augeas-0.5.1-1.el5.x86_64.rpm: (SHA1) DSA sha1 md5 (GPG) NOT OK (MISSING KEYS: GPG#217521f6) 
[root@server0 Local]# chown -R signer:signer .
[root@server0 Local]# su - signer
[signer@server0 ~]$ gpg --list-keys
/home/signer/.gnupg/pubring.gpg
-------------------------------
pub   2048R/44CB93FD 2009-07-23 [expires: 2019-07-21]
uid                  Repository Signer (Example Com) 

[signer@server0 ~]$ cat <.rpmmacros
> %_signature gpg
> %_gpg_name Repository Signer (Example Com) 
> EOF
[signer@server0 ~]$ cd /var/www/html/install/Local
[signer@server0 Local]$ rpm --resign *.rpm
Enter pass phrase: 
Pass phrase is good.
augeas-0.5.1-1.el5.x86_64.rpm:
augeas-libs-0.5.1-1.el5.x86_64.rpm:
facter-1.5.5-1.el5.noarch.rpm:
...
[signer@server0 Local]$ rpm -K augeas-0.5.1-1.el5.x86_64.rpm 
augeas-0.5.1-1.el5.x86_64.rpm: rsa sha1 (md5) pgp md5 OK

moving the rpms

Now that the rpms are signed by our key, we can move the rpms to the correct directories. If disk space it not at a premium, just copy the noarch rpms into both the i386 and the x86_64 directories. If you are concerned about usage (like me), hard link the noarch file in i386 to the one in x86_64 (they are the same file anyway).

[signer@server0 Local]$ mv *x86_64.rpm x86_64
[signer@server0 Local]$ for file in *noarch.rpm
> do
>  mv $file x86_64
>  ln x86_64/$file i386
> done

comps.xml (optional)

If you want to be able to install groups of packages from your repository, you'll need to define those groups in a comps.xml file. The syntax of the comps.xml file is fairly straightforward. You define groups, then within a group you define if a package is mandatory, required or optional. For our example, we can create a "Local Client" group that will install puppet, func and augeas.
<comps>
<!--  <meta> -->
<!-- Meta information will go here eventually -->
<!--  </meta> -->
  <group>
    <id>localclient</id>
    <name>Local Client</name>
    <default>true</default>
    <description>Default RPMS from Local Clients</description>
    <uservisible>true</uservisible>
    <packagelist>
      <packagereq type="default">puppet</packagereq>
      <packagereq type="default">ruby-augeas</packagereq>
      <packagereq type="default">facter</packagereq>
      <packagereq type="default">func</packagereq>
    </packagelist>
  </group>
</comps>

createrepo

Now that all the files are in place, we can run createrepo on our repository to generate the files necessary for yum.
[root@server0 install]# yum -y install createrepo
Setting up Install Process
Parsing package install arguments
Resolving Dependencies
--> Running transaction check
---> Package createrepo.noarch 0:0.4.4-2.fc6 set to be updated
--> Finished Dependency Resolution

Dependencies Resolved

========================================================================================================
 Package               Arch              Version                 Repository                        Size
========================================================================================================
Installing:
 createrepo            noarch            0.4.4-2.fc6             Install_Server_Client             37 k

Transaction Summary
========================================================================================================
Install      1 Package(s)         
Update       0 Package(s)         
Remove       0 Package(s)         

Total download size: 37 k
Downloading Packages:
Running rpm_check_debug
Running Transaction Test
Finished Transaction Test
Transaction Test Succeeded
Running Transaction
  Installing     : createrepo                                        [1/1] 

Installed: createrepo.noarch 0:0.4.4-2.fc6
Complete!
[root@server0 install]# cd Local
[root@server0 Local]# mv comps.xml x86_64
[root@server0 Local]# ln x86_64/comps.xml i386
[root@server0 Local]# createrepo -g comps.xml x86_64
9/9 - augeas-0.5.1-1.el5.x86_64.rpm                                             
Saving Primary metadata
Saving file lists metadata
Saving other metadata
[root@server0 Local]# createrepo -g comps.xml i386
5/5 - func-0.24-1.el5.noarch.rpm                                                
Saving Primary metadata
Saving file lists metadata
Saving other metadata
Now that our repo files are in place, we need to create an rpm for our repository so we can install it with kickstart or we can install a repo file and add the key to rpm using kickstart. Creating an rpm is probably cleaner. If you want to test this repo at this point, you can add the following file to /etc/yum.repos.d, and copy the RPM-GPG-KEY-example.com that we created earlier into /etc/pki/rpm-gpg (actually, since we already imported the key with rpm --import, yum shouldn't complain about keys at this point, but copying the file is a good idea).
[root@server0 yum.repos.d]# cp ~signer/RPM-GPG-KEY-example.com /etc/pki/rpm-gpg/
[root@server0 yum.repos.d]# cat local.repo
[Local]
name=Local RPMS $releasever - $basearch
baseurl=file:///var/www/html/install/Local/$basearch
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-example.com
enabled=1
[root@server0 yum.repos.d]# yum search augeas
Local                                                                            | 1.1 kB     00:00     
primary.xml.gz                                                                   | 2.8 kB     00:00     
Local                                                          7/7
=========================================== Matched: augeas ============================================
augeas.x86_64 : A library for changing configuration files
augeas-libs.x86_64 : Libraries for augeas
ruby-augeas.x86_64 : Ruby bindings for Augeas
[root@server0 yum.repos.d]# yum grouplist |grep Client
   Local Client

repository rpm (optional)

If you don't want to build a repository rpm, you can just put the local.repo file into your kickstart file. You will need to import the key using rpm --import also.

A somewhat better way to do it is to make a repository rpm that install the file and the key. To make such an rpm, create a working directory in signer's home directory

[root@server0 ~]# yum -y install rpm-build
[root@server0 ~]# su - signer
[signer@server0 ~]$ mkdir -p src/RPMS src/SPECS src/BUILD src/SRPMS
[signer@server0 ~]$ echo "%_topdir /home/signer/src" >> ~/.rpmmacros
[signer@server0 ~]$ cd src/SPECS
[signer@server0 SPECS]$ cat Example.com-local.spec
Summary: yum Local repository
Name: Example.com-Local
Version: 1
Release: 1
Group: System Environment/Base
License: GPL
BuildRoot: %{_tmppath}/%{name}-root
BuildArch: noarch

%description
This rpm contains the yum Example.com Local repository

%prep

%build

%install
mkdir -p $RPM_BUILD_ROOT/etc/yum.repos.d/
cat > $RPM_BUILD_ROOT/etc/yum.repos.d/local-%{version}-local.repo < $RPM_BUILD_ROOT/etc/pki/rpm-gpg/RPM-GPG-KEY-example.com <
- initial release

[signer@server0 SPECS]$ rpmbuild -ba Example.com-local.spec 
Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.10406
+ umask 022
+ cd /home/signer/src/BUILD
+ exit 0
Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.10406
+ umask 022
+ cd /home/signer/src/BUILD
+ exit 0
Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.10406
+ umask 022
+ cd /home/signer/src/BUILD
+ mkdir -p /var/tmp/Example.com-Local-root/etc/yum.repos.d/
+ cat
+ mkdir -p /var/tmp/Example.com-Local-root/etc/pki/rpm-gpg/
+ cat
+ exit 0
Processing files: Example.com-Local-1-1
Requires(interp): /bin/sh /bin/sh
Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
Requires(post): /bin/sh
Requires(postun): /bin/sh
Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/tmp/Example.com-Local-root
Wrote: /home/signer/src/SRPMS/Example.com-Local-1-1.src.rpm
Wrote: /home/signer/src/RPMS/noarch/Example.com-Local-1-1.noarch.rpm
Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.10406
+ umask 022
+ cd /home/signer/src/BUILD
+ rm -rf /var/tmp/Example.com-Local-root
+ exit 0
[signer@server0 SPECS]$ cd ../
[signer@server0 src]$ cp RPMS/noarch/Example.com-Local-1-1.noarch.rpm SRPMS/Example.com-Local-1-1.src.rpm /var/www/html/install/Local
Now we need to move the rpms into the correct directories and rerun createrepo. We should script this at this point. Here is a simple script (modified from the one we use) that checks if rpms are signed and then links them into the appropriate directory. By linking first, we can make multiple links and then remove the copy in the current directory.
[signer@server0 Local]$ mkdir ~/bin
[signer@server0 Local]$ cat ~/bin/update_repo 
#!/bin/sh

COMPS=comps.xml

# determine the architecture of the rpm (noarch x86_64 i386...)
rpm_arch() {
	echo $1 | awk -F'.' '{NF=NF-1; print $NF}'
}

# build a list of rpms to move
for i in $@ *.rpm; do
	if [ -f "$i" ]; then
		case $i in
			*rpm)
				if rpm -K $i | awk '/pgp/ && /OK/ && !/NOT OK/' &>/dev/null; then
					RPMLIST="$RPMLIST $i"
				else
					echo "ERROR: rpm $i is NOT SIGNED"
					exit 1
				fi
				;;
			*)
				echo "ERROR: $i is not an rpm"
				exit 1
				;;
		esac
	else
		if [ "XXX$i" != "XXX*.rpm" ]; then
			echo "ERROR: $i is not a file"
			exit 1
		fi
	fi
done

echo $RPMLIST
if [ -d i386 -a -d x86_64 -a -d SRPMS ]; then
	for i in $RPMLIST; do
		ARCH=`rpm_arch $i`
		case $ARCH in
			src)
				ARCH=SRPMS
				;;
			i386|i486|i586|i686)
				ARCH=i386
				;;
			x86_64)
				;;
			noarch)
				ARCH="i386 x86_64"
				;;
			*)
				ARCH=unknown
				echo "$i unknown architecture"
				;;
		esac
		ERROR=""
		if [ "$ARCH" != "unknown" ]; then
			for DESTARCH in $ARCH
			do
				if [ -e $DESTARCH/$i ]; then
					echo "$i already exists in $DESTARCH"
					ERROR=1
				else
					echo "linking $i into $DESTARCH"
					ln $i $DESTARCH
				fi
			done
			if [ -z "$ERROR" ]; then
				# linking was successful, remove file
				rm -f $i
			else
				echo "ERROR: could not link $i"
			fi
		fi
	done
	echo "Running createrepo now"	
	for ARCH in i386 x86_64
	do
		createrepo -g $COMPS $ARCH
	done
else
	echo "ERROR: required directories not found (i386 x86_64 SRPMS)"
fi
[signer@server0 Local]$ chmod 755 ~/bin/update_repo
[signer@server0 Local]$ ~/bin/update_repo 
ERROR: rpm Example.com-Local-1-1.noarch.rpm is NOT SIGNED
We forgot to sign the rpms we just built. Sign them now.
[signer@server0 Local]$ rpm --addsign *rpm
Enter pass phrase: 
Pass phrase is good.
Example.com-Local-1-1.noarch.rpm:
gpg: WARNING: standard input reopened
gpg: WARNING: standard input reopened
Example.com-Local-1-1.src.rpm:
gpg: WARNING: standard input reopened
gpg: WARNING: standard input reopened
Now try that update again
[signer@server0 Local]$ ~/bin/update_repo 
Example.com-Local-1-1.noarch.rpm Example.com-Local-1-1.src.rpm
linking Example.com-Local-1-1.noarch.rpm into i386
linking Example.com-Local-1-1.noarch.rpm into x86_64
linking Example.com-Local-1-1.src.rpm into SRPMS
Running createrepo now
4/4 - func-0.24-1.el5.noarch.rpm                                                
Saving Primary metadata
Saving file lists metadata
Saving other metadata
8/8 - augeas-0.5.1-1.el5.x86_64.rpm                                             
Saving Primary metadata
Saving file lists metadata
Saving other metadata
Now we have our rpm signed and in our repo and can install it at install time using kickstart.

putting the repo into kickstart

We can add the repository to our kickstart with the repo keyword. We can then specify packages or groups from the repository in the %packages section.

repo --name=Local --baseurl=http://server0.example.com/install/Local/x86_64
We will want our group to be installed as well as our repository rpm, here is how we do that.
@localclient
Now after installation our client will have the following packages installed from our repository
[root@client15 ~]# rpm -q puppet ruby-augeas func
puppet-0.24.8-1.el5.1
ruby-augeas-0.2.0-1.el5
func-0.24-1.el5
Now that we have our extra rpms installed, we can move on to configuring puppet.