r/repohost: Configure Yum package repo host
So it turns out Gitea's RPM package repository feature is less than stellar. Since each organization/user can only have a single repository, separating packages by OS would be extremely cumbersome. Presumably, the feature was designed for projects that only build a single PRM for each version, but most of my packages need multiple builds, as they tend to link to system libraries. Further, only the repository owner can publish to user-scoped repositories, so e.g. Jenkins cannot publish anything to a repository under my *dustin* account. This means I would ultimately have to create an Organization for every OS/version I need to support, and make Jenkins a member of it. That sounds tedious and annoying, so I decided against using that feature for internal packages. Instead, I decided to return to the old ways, publishing packages with `rsync` and serving them with Apache. It's fairly straightforward to set this up: just need a directory with the appropriate permissions for users to upload packages, and configure Apache to serve from it. One advantage Gitea's feature had over a plain directory is its automatic management of repository metadata. Publishers only have to upload the RPMs they want to serve, and Gitea handles generating the index, database, etc. files necessary to make the packages available to Yum/dnf. With a plain file host, the publisher would need to use `createrepo` to generate the repository metadata and upload that as well. For repositories with multiple packages, the publisher would need a copy of every RPM file locally in order for them to be included in the repository metadata. This, too, seems like it would be too much trouble to be tenable, so I created a simple automatic metadata manager for the file-based repo host. Using `inotifywatch`, the `repohost-createrepo` script watches for file modifications in the repository base directory. Whenever a file is added or changed, the directory containing it is added to a queue. Every thirty seconds, the queue is processed; for each unique directory in the queue, repository metadata are generated. This implementation combines the flexibility of a plain file host, supporting an effectively unlimited number of repositories with fully-configurable permissions, and the ease of publishing of a simple file upload.chrony
parent
6955c4e7ad
commit
c6f0ea9720
|
@ -0,0 +1,3 @@
|
|||
repohost_admins:
|
||||
- dustin
|
||||
- jenkins
|
3
hosts
3
hosts
|
@ -151,6 +151,9 @@ web0.pyrocufflink.blue
|
|||
[radius:children]
|
||||
samba-dc
|
||||
|
||||
[repohost]
|
||||
file0.pyrocufflink.blue
|
||||
|
||||
[rw-root]
|
||||
serial0.pyrocufflink.blue
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
- hosts: repohost
|
||||
roles:
|
||||
- role: repohost
|
||||
tags: repohost
|
|
@ -0,0 +1 @@
|
|||
repohost_admins: []
|
|
@ -0,0 +1,33 @@
|
|||
[Unit]
|
||||
Description=Watch and regenerate Yum repositories
|
||||
RequiresMountsFor=/srv/www/repohost
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/local/libexec/repohost-createrepo
|
||||
User=repohost
|
||||
DeviceAllow=
|
||||
DevicePolicy=closed
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
NoNewPrivileges=yes
|
||||
PrivateDevices=yes
|
||||
PrivateUsers=yes
|
||||
PrivateTmp=yes
|
||||
ProcSubset=pid
|
||||
ProtectClock=yes
|
||||
ProtectControlGroups=yes
|
||||
ProtectHome=yes
|
||||
ProtectHostname=yes
|
||||
ProtectKernelLogs=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectProc=invisible
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=/srv/www/repohost
|
||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||
RestrictNamespaces=yes
|
||||
RestrictRealtime=yes
|
||||
RestrictSUIDSGID=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -0,0 +1,35 @@
|
|||
#!/bin/sh
|
||||
|
||||
QFILE="${HOME}"/createrepo.queue
|
||||
REPOS_ROOT="${HOME}"/repos
|
||||
|
||||
createrepo_loop() {
|
||||
while sleep 30; do
|
||||
[ -f "${QFILE}" ] || continue
|
||||
mv "${QFILE}" "${QFILE}.work"
|
||||
sort -u "${QFILE}.work" > "${QFILE}.sorted"
|
||||
while read dir; do
|
||||
printf 'Generating repository metadata for %s\n' "${dir}"
|
||||
createrepo_c "${dir}"
|
||||
done < "${QFILE}.sorted"
|
||||
rm -f "${QFILE}.work" "${QFILE}.sorted"
|
||||
done
|
||||
}
|
||||
|
||||
inotify_loop() {
|
||||
stdbuf -o 0 inotifywait \
|
||||
--monitor \
|
||||
--event close_write,move,delete \
|
||||
--recursive \
|
||||
"${REPOS_ROOT}" \
|
||||
| stdbuf -o 0 grep -E '\.rpm$' \
|
||||
| while read dir _ _; do
|
||||
flock "${QFILE}" sh -c 'echo "$1" >> "$2"' -- "${dir}" "${QFILE}"
|
||||
done
|
||||
}
|
||||
|
||||
mkdir -p "${REPOS_ROOT}"
|
||||
|
||||
createrepo_loop &
|
||||
inotify_loop &
|
||||
wait
|
|
@ -0,0 +1,6 @@
|
|||
Alias /yum /srv/www/repohost/repos
|
||||
|
||||
<Directory /srv/www/repohost/repos/>
|
||||
Require all granted
|
||||
Options +Indexes
|
||||
</Directory>
|
|
@ -0,0 +1,9 @@
|
|||
- name: restart repohost-createrepo
|
||||
service:
|
||||
name: repohost-createrepo
|
||||
state: restarted
|
||||
|
||||
- name: reload httpd
|
||||
service:
|
||||
name: httpd
|
||||
state: reloaded
|
|
@ -0,0 +1,102 @@
|
|||
- name: ensure required packages are installed
|
||||
package:
|
||||
name:
|
||||
- createrepo_c
|
||||
- inotify-tools
|
||||
state: present
|
||||
tags:
|
||||
- install
|
||||
|
||||
- name: ensure repohost user exists
|
||||
user:
|
||||
name: repohost
|
||||
system: true
|
||||
createhome: false
|
||||
home: /srv/www/repohost
|
||||
|
||||
- name: ensure repohost base directory exists
|
||||
file:
|
||||
path: /srv/www/repohost
|
||||
owner: repohost
|
||||
group: repohost
|
||||
mode: u=rwx,go=rx
|
||||
state: directory
|
||||
|
||||
- name: ensure repohost repos directory exists
|
||||
file:
|
||||
path: /srv/www/repohost/repos
|
||||
owner: repohost
|
||||
group: repohost
|
||||
mode: ug=rwx,o=rx
|
||||
state: directory
|
||||
|
||||
- name: ensure repohost repos dir acl is set
|
||||
acl:
|
||||
entity: '{{ item }}'
|
||||
etype: user
|
||||
default: false
|
||||
permissions: rwx
|
||||
path: /srv/www/repohost/repos
|
||||
loop: '{{ repohost_admins }}'
|
||||
- name: ensure repohost repos dir default acl is set
|
||||
acl:
|
||||
entity: '{{ item }}'
|
||||
etype: user
|
||||
default: true
|
||||
permissions: rwx
|
||||
path: /srv/www/repohost/repos
|
||||
loop: '{{ repohost_admins }}'
|
||||
|
||||
- name: ensure repohost-createrepo script is installed
|
||||
copy:
|
||||
src: repohost-createrepo.sh
|
||||
dest: /usr/local/libexec/repohost-createrepo
|
||||
owner: root
|
||||
group: root
|
||||
mode: u=rwx,go=rx
|
||||
setype: bin_t
|
||||
notify:
|
||||
- restart repohost-createrepo
|
||||
tags:
|
||||
- repohost-createrepo
|
||||
- install
|
||||
|
||||
- name: ensure repohost-createrepo systemd service unit is installed
|
||||
copy:
|
||||
src: repohost-createrepo.service
|
||||
dest: /etc/systemd/system/repohost-createrepo.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: u=rw,go=r
|
||||
tags:
|
||||
- systemd
|
||||
|
||||
- name: ensure repohost-createrepo service starts at boot
|
||||
service:
|
||||
name: repohost-createrepo
|
||||
enabled: true
|
||||
tags:
|
||||
- service
|
||||
|
||||
- name: flush handlers
|
||||
meta: flush_handlers
|
||||
|
||||
- name: ensure repohost-createrepo service is running
|
||||
service:
|
||||
name: repohost-createrepo
|
||||
state: started
|
||||
tags:
|
||||
- service
|
||||
|
||||
- name: ensure apache is configured to serve yum repos
|
||||
copy:
|
||||
src: yumrepos.httpd.conf
|
||||
dest: /etc/httpd/conf.d/yumrepos.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: u=rw,go=r
|
||||
notify:
|
||||
- reload httpd
|
||||
tags:
|
||||
- config
|
||||
- httpd-config
|
Loading…
Reference in New Issue