Hosting a CentOS Stream 9 Repo Locally in your Lab

I have been doing a lot of lab work lately and it has occurred to me that it would be pretty cool to run a 2 vcpu vm with 2gb ram, minimal resources in the lab, to host our own x86 repository for my favourite OS of choice, centos. It’s really easy to do too thanks to yum-utils.

# cat make-centos9-repo-server.sh
virt-install --name centos9-repo-mirror --memory 2048 --vcpus 2 --disk=size=500,backing_store=/var/lib/libvirt/images/CentOS-Stream-GenericCloud-9-latest.x86_64.qcow2 --cloud-init user-data=./cloud-init.yaml,meta-data=./centos9-repo-server.yaml,disable=on --network bridge=virbr0 --osinfo=centos-stream9 --noautoconsole

Using my current lab template above, I simply alter a few things like name, vcpus, and increase the initial disk space to 500GB. Which should be ample for now.

Creating the repository mirrors

sudo dnf install yum-utils

#create repo directory path and subdirectory structure
mkdir -p /var/www/repos/centos-stream/9/x86_64/os

chmod -R 755 /var/www/repos

# copy from official repository
reposync -p /var/www/repos/centos-stream/9/x86_64/os/ --repo=baseos --download-metadata
reposync -p /var/www/repos/centos-stream/9/x86_64/os/ --repo=appstream --download-metadata
reposync -p /var/www/repos/centos-stream/9/x86_64/os/ --repo=extras-common --download-metadata 

Configuring a cronjob to automatically resync the repo daily

vi /etc/cron.daily/update-repo
# create new

#!/bin/bash

VER='9'
ARCH='x86_64'
REPOS=(baseos appstream extras-common)

for REPO in ${REPOS[@]}
do
    reposync -p /var/www/repos/centos-stream/${VER}/${ARCH}/os/ --repo=${REPO} --download-metadata --newest-only
done
sudo restorecon -r /var/www/repos/

# exit vim :wq and chmod the cronfile for good measure
chmod 755 /etc/cron.daily/update-repo 

Then we install the httpd server and configure it for this path

vi /etc/httpd/conf.d/repos.conf
# create new

Alias /repos /var/www/repos
<directory /var/www/repos>
    Options +Indexes
    Require all granted
</directory>

#exit vim and restart the httpd with the new root repo
systemctl restart httpd 

Finally we add firewall rule

firewall-cmd --add-service=http --permanent
systemctl restart firewalld

All done. Now we can configure the local server with our golden image cloud image we’re using to boot from libvirt with our own local repo. Ideally we’d also have a local dns server so we can run mirror.some.tld or similar. In this case the ip address will for now suffice, with a hostname in /etc/hosts of the golden image like local-mirror <centos9-local-repo-ip>, instead so it looks pretty and recognisable in the repo file.

vi /etc/yum.repos.d/centos.repo
# change to local mirror server

[baseos]
name=CentOS Stream $releasever - BaseOS
#metalink=https://mirrors.centos.org/metalink?repo=centos-baseos-$stream&arch=$basearch&protocol=https,http
baseurl=http://local-mirror/repos/centos-stream/$releasever/$basearch/os/baseos/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
countme=1
enabled=1

[appstream]
name=CentOS Stream $releasever - AppStream
#metalink=https://mirrors.centos.org/metalink?repo=centos-appstream-$stream&arch=$basearch&protocol=https,http
baseurl=http://local-mirror/repos/centos-stream/$releasever/$basearch/os/appstream/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
countme=1
enabled=1

# vi /etc/yum.repos.d/centos-addons.repo
# change to local mirror server

[extras-common]
name=CentOS Stream $releasever - Extras packages
#metalink=https://mirrors.centos.org/metalink?repo=centos-extras-sig-extras-common-$stream&arch=$basearch&protocol=https,http
baseurl=http://local-mirror/repos/centos-stream/$releasever/$basearch/os/extras-common/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Extras-SHA512
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
countme=1
enabled=1


Finally clean and re-update yum to test config change

dnf clean all
dnf repolist 

If you are insisting on running selinux like me, make sure you allow filegetattr;

[adam@centos9-repo-server appstream]$ sudo audit2allow -a -M filegetattr
******************** IMPORTANT ***********************
To make this policy package active, execute:

semodule -i filegetattr.pp

[adam@centos9-repo-server appstream]$ semodule -i filegetattr.pp
libsemanage.semanage_create_store: Could not read from module store, active modules subdirectory at /var/lib/selinux/targeted/active/modules. (Permission denied).
libsemanage.semanage_direct_connect: could not establish direct connection (Permission denied).
semodule:  Could not connect to policy handler
[adam@centos9-repo-server appstream]$ sudo semodule -i filegetattr.pp

It wouldn’t go amiss to tag the files either however in this case chcon isn’t necessary because the /var/www context already has the correct mask by default in selinux. 😀

As it turns out you also need read, as well as getattr to actually download the packages (getattr is needed for listing them i.e. ls)

$ sudo audit2allow -a


#============= httpd_t ==============

#!!!! This avc is allowed in the current policy
allow httpd_t var_t:file getattr;
allow httpd_t var_t:file read;
[adam@centos9-repo-server appstream]$ sudo audit2allow -a -M httpdread.pp
[adam@centos9-repo-server appstream]$ sudo semodule -i httpdread.pp

Once again we retry. OK, turns out we do need to set the unconfined files/folder that were written; which can be done like

 sudo restorecon -r /var/www/repos/

I guess we’ll add this restorecon -r /var/www/repos/ to our cronjob for good measure to make sure the context is right.