forked from Lainports/freebsd-ports
562 lines
12 KiB
Bash
Executable file
562 lines
12 KiB
Bash
Executable file
#!/bin/sh
|
|
# $FreeBSD$
|
|
|
|
# server-side script to handle various commands common to builds
|
|
|
|
# configurable variables
|
|
pb=/var/portbuild
|
|
|
|
# XXX unused
|
|
get_latest_snap() {
|
|
snap=$1
|
|
|
|
zfs list -rHt snapshot ${snap} | tail -1 | awk '{print $1}'
|
|
}
|
|
|
|
now() {
|
|
date +%Y%m%d%H%M%S
|
|
}
|
|
|
|
do_list() {
|
|
arch=$1
|
|
branch=$2
|
|
|
|
buildpar=/var/portbuild/${arch}/${branch}/builds
|
|
|
|
if [ -d ${buildpar} ]; then
|
|
snaps=$(cd ${buildpar}; ls -1d 2* 2> /dev/null)
|
|
echo "The following builds are active:"
|
|
echo ${snaps}
|
|
|
|
if [ -L ${buildpar}/latest -a -d ${buildpar}/latest/ ]; then
|
|
link=$(readlink ${buildpar}/latest)
|
|
link=${link%/}
|
|
link=${link##*/}
|
|
|
|
echo "Latest build is: ${link}"
|
|
fi
|
|
else
|
|
echo "No such build environment ${arch}/${branch}"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
do_create() {
|
|
arch=$1
|
|
branch=$2
|
|
buildid=$3
|
|
shift 3
|
|
|
|
archivedir=${pb}/${arch}/archive
|
|
buildlogsdir=${archivedir}/buildlogs
|
|
if [ ! -d ${buildlogsdir} ]; then
|
|
mkdir -p ${buildlogsdir} || exit 1
|
|
chown -R ports-${arch}:portmgr ${archivedir}
|
|
chmod -R g+w ${archivedir}
|
|
fi
|
|
|
|
archdir=${pbab}/builds
|
|
if [ ! -d ${archdir} ]; then
|
|
mkdir -p ${archdir} || exit 1
|
|
chown -R ports-${arch}:portmgr ${pbab}
|
|
chmod -R g+w ${pbab}
|
|
fi
|
|
|
|
builddir=$(realpath ${archdir})/${buildid}
|
|
if [ -d ${builddir} ]; then
|
|
echo "Can't create ${builddir}, it already exists"
|
|
exit 1
|
|
fi
|
|
|
|
mountpoint=${builddir}
|
|
newfs=${ZFS_VOLUME}/portbuild/${arch}/${buildid}
|
|
zfs create -o mountpoint=${mountpoint} ${newfs} || exit 1
|
|
chown -R ports-${arch}:portmgr ${mountpoint}
|
|
chmod -R g+w ${mountpoint}
|
|
|
|
do_portsupdate_inner ${arch} ${branch} ${buildid} ${builddir} $@
|
|
|
|
do_srcupdate_inner ${arch} ${branch} ${buildid} ${builddir} $@
|
|
|
|
ln -sf ${builddir} ${pbab}/builds/latest
|
|
|
|
echo "New build ID is ${buildid}"
|
|
}
|
|
|
|
do_clone() {
|
|
arch=$1
|
|
branch=$2
|
|
buildid=$3
|
|
builddir=$4
|
|
shift 4
|
|
|
|
if [ "$#" -gt 0 ]; then
|
|
newid=$1
|
|
shift
|
|
else
|
|
newid=$(now)
|
|
fi
|
|
|
|
tmp=$(realpath ${builddir})
|
|
tmp=${tmp%/}
|
|
newbuilddir="${tmp%/*}/${newid}"
|
|
|
|
oldfs=${ZFS_VOLUME}/portbuild/${arch}/${buildid}
|
|
newfs=${ZFS_VOLUME}/portbuild/${arch}/${newid}
|
|
|
|
zfs snapshot ${oldfs}@${newid}
|
|
zfs clone ${oldfs}@${newid} ${newfs}
|
|
zfs set mountpoint=${newbuilddir} ${newfs}
|
|
zfs promote ${newfs}
|
|
|
|
if zfs list -H -t filesystem ${oldfs}/ports 2> /dev/null; then
|
|
portsnap=${oldfs}/ports@${newid}
|
|
zfs snapshot ${portsnap}
|
|
zfs clone ${portsnap} ${newfs}/ports
|
|
zfs promote ${newfs}/ports
|
|
fi
|
|
|
|
if zfs list -H -t filesystem ${oldfs}/src 2> /dev/null; then
|
|
srcsnap=${oldfs}/src@${newid}
|
|
zfs snapshot ${srcsnap}
|
|
zfs clone ${srcsnap} ${newfs}/src
|
|
zfs promote ${newfs}/src
|
|
fi
|
|
|
|
if [ -d ${newbuilddir} ]; then
|
|
if [ ! -f ${pbab}/builds/previous/.keep ]; then
|
|
/var/portbuild/scripts/build destroy ${arch} ${branch} previous
|
|
fi
|
|
rm -f ${pbab}/builds/previous
|
|
mv ${pbab}/builds/latest ${pbab}/builds/previous
|
|
|
|
ln -sf ${newbuilddir} ${pbab}/builds/latest
|
|
fi
|
|
|
|
echo "New build ID is ${newid}"
|
|
}
|
|
|
|
do_portsupdate() {
|
|
arch=$1
|
|
branch=$2
|
|
buildid=$3
|
|
builddir=$4
|
|
shift 4
|
|
if [ $# -gt 0 ]; then
|
|
arg=$1
|
|
shift
|
|
fi
|
|
|
|
destroy_fs ${ZFS_VOLUME}/portbuild/${arch} ${buildid} /ports || exit 1
|
|
|
|
if [ "${arg}" = "-umount" ]; then
|
|
return
|
|
fi
|
|
|
|
do_portsupdate_inner ${arch} ${branch} ${buildid} ${builddir} $@
|
|
}
|
|
|
|
do_portsupdate_inner() {
|
|
arch=$1
|
|
branch=$2
|
|
buildid=$3
|
|
builddir=$4
|
|
shift 4
|
|
|
|
echo "================================================"
|
|
echo "Reimaging ZFS ports tree on ${builddir}/ports"
|
|
echo "================================================"
|
|
|
|
portsfs=${ZFS_VOLUME}/portbuild/${arch}/${buildid}/ports
|
|
now=$(now)
|
|
|
|
zfs snapshot ${ZFS_VOLUME}/${SNAP_PORTS_DIRECTORY}/ports@${now}
|
|
zfs clone ${ZFS_VOLUME}/${SNAP_PORTS_DIRECTORY}/ports@${now} ${portsfs}
|
|
zfs set mountpoint=${builddir}/ports ${portsfs}
|
|
}
|
|
|
|
do_srcupdate() {
|
|
arch=$1
|
|
branch=$2
|
|
buildid=$3
|
|
builddir=$4
|
|
shift 4
|
|
if [ $# -gt 0 ]; then
|
|
arg=$1
|
|
shift
|
|
fi
|
|
|
|
destroy_fs ${ZFS_VOLUME}/portbuild/${arch} ${buildid} /src || exit 1
|
|
|
|
if [ "${arg}" = "-umount" ]; then
|
|
return
|
|
fi
|
|
|
|
do_srcupdate_inner ${arch} ${branch} ${buildid} ${builddir} $@
|
|
}
|
|
|
|
do_srcupdate_inner() {
|
|
arch=$1
|
|
branch=$2
|
|
buildid=$3
|
|
builddir=$4
|
|
shift 4
|
|
|
|
echo "================================================"
|
|
echo "Reimaging ZFS src tree on ${builddir}/src"
|
|
echo "================================================"
|
|
|
|
strippedbranch=${branch%%[-\.]*}
|
|
srcfs=${ZFS_VOLUME}/portbuild/${arch}/${buildid}/src
|
|
now=$(now)
|
|
|
|
zfs snapshot ${ZFS_VOLUME}/${SNAP_SRC_DIRECTORY_PREFIX}${strippedbranch}/src@${now}
|
|
zfs clone ${ZFS_VOLUME}/${SNAP_SRC_DIRECTORY_PREFIX}${strippedbranch}/src@${now} ${srcfs}
|
|
zfs set mountpoint=${builddir}/src ${srcfs}
|
|
}
|
|
|
|
cleanup_client() {
|
|
arch=$1
|
|
branch=$2
|
|
buildid=$3
|
|
mach=$4
|
|
arg=$5
|
|
|
|
# XXX use same exclusion protocol as claim-chroot
|
|
|
|
echo "Started cleaning up ${arch}/${branch} build ID ${buildid} on ${mach}"
|
|
|
|
test -f ${pb}/${arch}/portbuild.${mach} && . ${pb}/${arch}/portbuild.${mach}
|
|
|
|
# Kill off builds and clean up chroot
|
|
${pb}/scripts/dosetupnode ${arch} ${branch} ${buildid} ${mach} -nocopy -queue -full
|
|
|
|
echo "Finished cleaning up ${arch}/${branch} build ID ${buildid} on ${mach}"
|
|
}
|
|
|
|
do_cleanup() {
|
|
arch=$1
|
|
branch=$2
|
|
buildid=$3
|
|
builddir=$4
|
|
arg=$5
|
|
shift 5
|
|
|
|
for i in `cat ${pb}/${arch}/mlist`; do
|
|
cleanup_client ${arch} ${branch} ${buildid} ${i} ${arg} &
|
|
done
|
|
wait
|
|
}
|
|
|
|
do_upload() {
|
|
arch=$1
|
|
branch=$2
|
|
buildid=$3
|
|
builddir=$4
|
|
shift 4
|
|
|
|
echo "Not implemented yet"
|
|
exit 1
|
|
|
|
}
|
|
|
|
test_fs() {
|
|
local fs=$1
|
|
|
|
zfs list -Ht filesystem ${fs} > /dev/null 2>&1
|
|
|
|
}
|
|
|
|
|
|
get_latest_child() {
|
|
local fs=$1
|
|
|
|
# Return the child of this filesystem with lexicographically
|
|
# highest name
|
|
#
|
|
# XXX if a filesystem is cloned into a different prefix
|
|
# (e.g. different arch) then we may not get the most recent one
|
|
# but that should not happen.
|
|
zfs get -H -o name,value origin | grep ${fs} | sort | \
|
|
(while read zfs origin; do
|
|
if [ "${origin%@*}" = "${fs}" ]; then
|
|
child=${zfs}
|
|
fi
|
|
done; echo ${child})
|
|
}
|
|
|
|
get_parent() {
|
|
local fs=$1
|
|
|
|
# Check whether this filesystem has a parent
|
|
zfs get -H -o value origin ${fs} | \
|
|
(read snap;
|
|
case "${snap}" in
|
|
-|${ZFS_VOLUME}/${SNAP_DIRECTORY}/*)
|
|
;;
|
|
*)
|
|
parent=${snap}
|
|
;;
|
|
esac; echo ${parent})
|
|
}
|
|
|
|
destroy_fs() {
|
|
fs=$1
|
|
buildid=$2
|
|
subfs=$3
|
|
|
|
fullfs=${fs}/${buildid}${subfs}
|
|
if test_fs "${fullfs}"; then
|
|
|
|
# We can destroy a leaf filesystem (having no dependent
|
|
# clones) with no further effort. However if we are
|
|
# destroying the root of the clone tree then we have to
|
|
# promote a child to be the new root.
|
|
#
|
|
# XXX In principle we might have to iterate until we end up as
|
|
# a leaf but I don't know if this can happen.
|
|
echo "Filesystem ${fullfs}"
|
|
child=$(get_latest_child ${fullfs})
|
|
parent=$(get_parent ${fullfs})
|
|
echo "Filesystem has parent ${parent}"
|
|
if [ -z "${child}" ]; then
|
|
echo "Filesystem is a leaf"
|
|
else
|
|
echo "Filesystem has latest child ${child}"
|
|
# Check whether filesystem is root
|
|
if [ -z "${parent}" ]; then
|
|
echo "Filesystem is root; promoting ${child}"
|
|
zfs promote ${child}
|
|
parent=$(get_parent ${fullfs})
|
|
echo "New parent is ${parent}"
|
|
else
|
|
echo "Filesystem has parent ${parent} and cannot be destroyed"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# We might have snapshots on the target filesystem, e.g. if it
|
|
# is both the head and tail of its clone tree. They should be
|
|
# unreferenced.
|
|
# We have to grep because zfs list -H returns an error instead of
|
|
# a null list if no snapshots exist
|
|
if ! (zfs list -r -H -o name -t snapshot ${fullfs} | grep "^${fullfs}@" | xargs -n 1 zfs destroy); then
|
|
return 1
|
|
fi
|
|
|
|
# The target filesystem should now be unreferenced
|
|
if ! zfs destroy -f "${fullfs}"; then
|
|
return 1
|
|
fi
|
|
|
|
# Destroy the origin snapshot, which should be unreferenced
|
|
if [ ! -z "${parent}" ]; then
|
|
if ! zfs destroy -f ${parent}; then
|
|
return 1
|
|
fi
|
|
fi
|
|
fi
|
|
}
|
|
|
|
do_destroy() {
|
|
arch=$1
|
|
branch=$2
|
|
buildid=$3
|
|
builddir=$4
|
|
shift 4
|
|
|
|
buildid=$(resolve ${pb} ${arch} ${branch} ${buildid})
|
|
if [ -z "${buildid}" ]; then
|
|
echo "Invalid build ID ${buildid}"
|
|
exit 1
|
|
fi
|
|
|
|
latestid=$(resolve ${pb} ${arch} ${branch} latest)
|
|
if [ "${buildid}" = "${latestid}" ]; then
|
|
echo "Cannot destroy latest build"
|
|
exit 1
|
|
fi
|
|
|
|
destroy_fs ${ZFS_VOLUME}/portbuild/${arch} ${buildid} /ports || exit 1
|
|
destroy_fs ${ZFS_VOLUME}/portbuild/${arch} ${buildid} /src || exit 1
|
|
destroy_fs ${ZFS_VOLUME}/portbuild/${arch} ${buildid} || exit 1
|
|
|
|
rmdir ${builddir}
|
|
|
|
}
|
|
|
|
# Run a command as root if running as user
|
|
# Authentication and command validation is taken care of by buildproxy
|
|
proxy_root() {
|
|
cmd=$1
|
|
arch=$2
|
|
branch=$3
|
|
buildid=$4
|
|
builddir=$5
|
|
shift 5
|
|
args=$@
|
|
|
|
id=$(id -u)
|
|
if [ ${id} != "0" ]; then
|
|
/var/portbuild/scripts/buildproxy-client "build ${cmd} ${arch} ${branch} ${buildid} ${args}"
|
|
error=$?
|
|
if [ ${error} -eq 254 ]; then
|
|
echo "Proxy error"
|
|
fi
|
|
else
|
|
eval "do_${cmd} ${arch} ${branch} ${buildid} ${builddir} ${args}"
|
|
error=$?
|
|
fi
|
|
|
|
exit ${error}
|
|
}
|
|
|
|
# Run a command as the ports-${arch} user if root
|
|
proxy_user() {
|
|
cmd=$1
|
|
arch=$2
|
|
branch=$3
|
|
buildid=$4
|
|
builddir=$5
|
|
shift 5
|
|
args=$@
|
|
|
|
id=$(id -u)
|
|
if [ ${id} != "0" ]; then
|
|
eval "do_${cmd} ${arch} ${branch} ${buildid} \"${builddir}\" ${args}"
|
|
error=$?
|
|
else
|
|
su ports-${arch} -c "/var/portbuild/scripts/build ${cmd} ${arch} ${branch} ${buildid} \"${builddir}\" ${args}"
|
|
error=$?
|
|
fi
|
|
|
|
exit ${error}
|
|
}
|
|
|
|
usage () {
|
|
echo "usage: build <command> <arch> <branch> [<buildid>] [<options> ...]"
|
|
exit 1
|
|
}
|
|
|
|
##################
|
|
|
|
if [ $# -lt 3 ]; then
|
|
usage
|
|
fi
|
|
|
|
cmd=$1
|
|
arch=$2
|
|
branch=$3
|
|
shift 3
|
|
|
|
. ${pb}/conf/server.conf
|
|
. ${pb}/${arch}/portbuild.conf
|
|
. ${pb}/scripts/buildenv
|
|
|
|
pbab=${pb}/${arch}/${branch}
|
|
|
|
validate_env ${arch} ${branch} || exit 1
|
|
|
|
# Not every command requires a buildid as arg
|
|
if [ $# -ge 1 ]; then
|
|
buildid=$1
|
|
shift 1
|
|
|
|
# Most commands require a buildid that is valid on the server. The
|
|
# exception is "cleanup" which is cleaning up a client build that may
|
|
# already be destroyed on the server.
|
|
case "$cmd" in
|
|
cleanup)
|
|
# Resolve symlinks but don't bail if the build doesn't exist.
|
|
newbuildid=$(resolve ${pb} ${arch} ${branch} ${buildid})
|
|
if [ ! -z "${newbuildid}" -a "${newbuildid}" != "${buildid}" ]; then
|
|
echo "Resolved ${buildid} to ${newbuildid}"
|
|
buildid=${newbuildid}
|
|
|
|
builddir=$(realpath ${pbab}/builds/${buildid}/)
|
|
# We can't rely on buildenv for this code path
|
|
fi
|
|
;;
|
|
create)
|
|
# XXX some way to avoid the latest/previous dance?
|
|
if [ -z "${buildid}" -o "${buildid}" = "latest" ]; then
|
|
buildid=$(now)"."`hostname -s`
|
|
elif [ "${buildid}" = "previous" ]; then
|
|
echo "Use build clone latest instead"
|
|
exit 1
|
|
else
|
|
buildid=${buildid%/}
|
|
fi
|
|
# We can't rely on buildenv for this code path
|
|
;;
|
|
*)
|
|
newbuildid=$(resolve ${pb} ${arch} ${branch} ${buildid})
|
|
if [ -z "${newbuildid}" ]; then
|
|
echo "Build ID ${buildid} does not exist"
|
|
exit 1
|
|
fi
|
|
if [ ${newbuildid} != ${buildid} ]; then
|
|
echo "Resolved ${buildid} to ${newbuildid}"
|
|
buildid=${newbuildid}
|
|
fi
|
|
|
|
builddir=$(realpath ${pbab}/builds/${buildid}/)
|
|
|
|
buildenv ${pb} ${arch} ${branch} ${builddir}
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# Unprivileged commands
|
|
case "$cmd" in
|
|
list)
|
|
do_list ${arch} ${branch} $@ || exit 1
|
|
;;
|
|
create)
|
|
if [ -z "${buildid}" ]; then
|
|
usage
|
|
fi
|
|
proxy_root create ${arch} ${branch} ${buildid} $@ || exit 1
|
|
;;
|
|
clone)
|
|
if [ -z "${buildid}" ]; then
|
|
usage
|
|
fi
|
|
proxy_root clone ${arch} ${branch} ${buildid} ${builddir} $@ || exit 1
|
|
;;
|
|
portsupdate)
|
|
if [ -z "${buildid}" ]; then
|
|
usage
|
|
fi
|
|
proxy_root portsupdate ${arch} ${branch} ${buildid} ${builddir} $@ || exit 1
|
|
;;
|
|
srcupdate)
|
|
if [ -z "${buildid}" ]; then
|
|
usage
|
|
fi
|
|
proxy_root srcupdate ${arch} ${branch} ${buildid} ${builddir} $@ || exit 1
|
|
;;
|
|
cleanup)
|
|
if [ -z "${buildid}" ]; then
|
|
usage
|
|
fi
|
|
# builddir may be null if cleaning up a destroyed build
|
|
proxy_user cleanup ${arch} ${branch} ${buildid} "${builddir}" $@ || exit 1
|
|
;;
|
|
upload)
|
|
if [ -z "${buildid}" ]; then
|
|
usage
|
|
fi
|
|
proxy_user upload ${arch} ${branch} ${buildid} ${builddir} $@ || exit 1
|
|
;;
|
|
destroy)
|
|
if [ -z "${buildid}" ]; then
|
|
usage
|
|
fi
|
|
proxy_root destroy ${arch} ${branch} ${buildid} ${builddir} $@ || exit 1
|
|
;;
|
|
*)
|
|
echo "build: invalid command: $cmd"
|
|
exit 1
|
|
;;
|
|
esac
|