Files
zoneminder/utils/do_debian_package.sh
T
Isaac ConnorandClaude Opus 5 d8353aa3c3 ci: make package versions unique per build
The snapshot component is date plus the number of commits since version.txt
last changed, so every build of the same tip on the same day produces the same
version string. The repo operator reports 96% of version+dist pairs published
more than once, and 1.39.34~20260912.22-bookworm1 uploaded six times in about
half an hour, each upload overwriting the last with different bytes.

Nothing on the repo server can close that. Reindexing shrinks the window but a
client that read Packages before a republish and fetched the .deb after it still
gets a hash mismatch, because the version no longer identifies the content.

Append GITHUB_RUN_ID and GITHUB_RUN_ATTEMPT, which are unique per build and
never reused. Outside Actions the string is unchanged, so a local build still
produces the version it always did.

Checked that the longer string still sorts after the versions already published
and before the next version.txt bump, with dpkg --compare-versions and
rpm.vercmp.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkQwahn9pi1y4wJe9BTxjM
2026-09-12 11:54:25 -04:00

426 lines
12 KiB
Bash
Executable File

#!/bin/bash
if [ "$1" == "clean" ]; then
read -p "Do you really want to delete existing packages? [y/N]"
[[ $REPLY == [yY] ]] && { rm -fr zoneminder*.build zoneminder*.changes zoneminder*.deb; echo "Existing package files deleted"; } || { echo "Packages have NOT been deleted"; }
exit;
fi
DEBUILD=`which debuild`;
if [ "$DEBUILD" == "" ]; then
echo "You must install the devscripts package. Try sudo apt-get install devscripts";
exit 1;
else
echo "Found debuild at ${DEBUILD}"
fi
for i in "$@"
do
case $i in
-b=*|--branch=*)
BRANCH="${i#*=}"
shift # past argument=value
;;
-d=*|--distro=*)
DISTROS="${i#*=}"
shift # past argument=value
;;
-i=*|--interactive=*)
INTERACTIVE="${i#*=}"
shift # past argument=value
;;
-p=*|--ppa=*)
PPA="${i#*=}"
shift # past argument=value
;;
-r=*|--release=*)
RELEASE="${i#*=}"
shift
;;
-s=*|--snapshot=*)
SNAPSHOT="${i#*=}"
shift # past argument=value
;;
-t=*|--type=*)
TYPE="${i#*=}"
shift # past argument=value
;;
-u=*|--urgency=*)
URGENCY="${i#*=}"
shift # past argument=value
;;
-f=*|--fork=*)
GITHUB_FORK="${i#*=}"
shift # past argument=value
;;
-v=*|--version=*)
PACKAGE_VERSION="${i#*=}"
shift
;;
-l=*|--local-source=*)
LOCAL_SOURCE="${i#*=}"
shift
;;
-x=*|--debbuild-extra=*)
DEBBUILD_EXTRA="${i#*=}"
shift
;;
--dput=*)
DPUT="${i#*=}"
shift
;;
--default)
DEFAULT=YES
shift # past argument with no value
;;
*)
# Treat trailing positional argument as local source directory
if [ -d "$i" ]; then
LOCAL_SOURCE="$i"
else
read -p "Unknown option $i, continue? (Y|n)"
[[ $REPLY == [yY] ]] && { echo "continuing..."; } || exit 1;
fi
;;
esac
done
DATE=`date -R`
if [ "$TYPE" == "" ]; then
echo "Defaulting to source build"
TYPE="source";
else
echo "Doing $TYPE build"
fi;
if [ "$DISTROS" == "" ]; then
DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`;
echo "Defaulting to $DISTROS for distribution";
else
echo "Building for $DISTROS";
fi;
# Release is a special mode... it uploads to the release ppa and cannot have a snapshot
if [ "$RELEASE" != "" ]; then
if [ "$SNAPSHOT" != "" ]; then
echo "Releases cannot have a snapshot.... exiting."
exit 0;
fi
if [ "$GITHUB_FORK" != "" ] && [ "$GITHUB_FORK" != "ZoneMinder" ]; then
echo "Releases cannot have a fork ($GITHUB_FORK).... exiting."
exit 0;
else
GITHUB_FORK="ZoneMinder";
fi
# We use a tag instead of a branch atm.
BRANCH=$RELEASE
else
if [ "$GITHUB_FORK" == "" ]; then
echo "Defaulting to ZoneMinder upstream git"
GITHUB_FORK="ZoneMinder"
fi;
fi;
# Instead of cloning from github each time, if we have a fork lying around, update it and pull from there instead.
if [ ! -d "${GITHUB_FORK}_zoneminder_release" ]; then
if [ "$LOCAL_SOURCE" != "" ]; then
if [ ! -d "$LOCAL_SOURCE" ]; then
echo "Local source directory $LOCAL_SOURCE does not exist."
exit 1;
fi
echo "Using local source directory $LOCAL_SOURCE as-is (no pull)."
echo "git clone $LOCAL_SOURCE ${GITHUB_FORK}_zoneminder_release"
git clone "$LOCAL_SOURCE" "${GITHUB_FORK}_zoneminder_release"
elif [ -d "${GITHUB_FORK}_ZoneMinder.git" ]; then
echo "Using local clone ${GITHUB_FORK}_ZoneMinder.git to pull from."
cd "${GITHUB_FORK}_ZoneMinder.git"
echo "git pull..."
git pull
cd ../
echo "git clone ${GITHUB_FORK}_ZoneMinder.git ${GITHUB_FORK}_zoneminder_release"
git clone "${GITHUB_FORK}_ZoneMinder.git" "${GITHUB_FORK}_zoneminder_release"
else
echo "git clone https://github.com/$GITHUB_FORK/ZoneMinder.git ${GITHUB_FORK}_zoneminder_release"
git clone "https://github.com/$GITHUB_FORK/ZoneMinder.git" "${GITHUB_FORK}_zoneminder_release"
fi
else
echo "release dir already exists. Please remove it."
exit 1;
fi;
cd "${GITHUB_FORK}_zoneminder_release"
if [ "$SNAPSHOT" == "stable" ]; then
if [ "$BRANCH" == "" ]; then
#REV=$(git rev-list --tags --max-count=1)
BRANCH=`git describe --tags $(git rev-list --tags --max-count=1)`;
if [ -z "$BRANCH" ]; then
# This should only happen in CI environments where tag info isn't available
BRANCH=$(cat "$(find . -maxdepth 1 -name 'version' -o -name 'version.txt')")
echo "Building branch $BRANCH"
fi
if [ "$BRANCH" == "" ]; then
echo "Unable to determine latest stable branch!"
exit 1;
fi
echo "Latest stable branch is $BRANCH";
fi;
else
if [ "$BRANCH" == "" ]; then
echo "Defaulting to master branch";
BRANCH="master";
fi;
if [ "$SNAPSHOT" == "NOW" ]; then
SNAPSHOT=`date +%Y%m%d%H%M%S`;
else
if [ "$SNAPSHOT" == "CURRENT" ]; then
# git the latest (short) commit hash of the version file
versionhash=$(git log -n1 --pretty=format:%h version.txt)
# Number of commits since the version file was last changed
numcommits=$(git rev-list ${versionhash}..HEAD --count)
SNAPSHOT="`date +%Y%m%d.`${numcommits}"
# date+numcommits is not unique per build: every build of the same tip on
# the same day produces the same string, and the repo then holds one
# version whose content changed underneath it. A client that read Packages
# before a republish and fetched the .deb after it gets a hash mismatch.
# GITHUB_RUN_ID is unique and never reused, and appending a component
# keeps the version strictly greater than the ones already published.
if [ -n "$GITHUB_RUN_ID" ]; then
SNAPSHOT="${SNAPSHOT}.${GITHUB_RUN_ID}.${GITHUB_RUN_ATTEMPT:-1}"
fi
fi;
fi;
fi;
echo "git checkout $BRANCH"
git checkout $BRANCH
if [ $? -ne 0 ]; then
echo "Failed to switch to branch."
exit 1;
fi;
if [ "$LOCAL_SOURCE" == "" ]; then
echo "git pull..."
git pull
else
echo "Skipping pull (using local source as-is)."
fi
# Grab the ZoneMinder version from the contents of the version file
VERSION=$(cat "$(find . -maxdepth 1 -name 'version' -o -name 'version.txt')")
if [ -z "$VERSION" ]; then
exit 1;
fi;
IFS='.' read -r -a VERSION_PARTS <<< "$VERSION"
cd ../
if [ "$SNAPSHOT" != "stable" ] && [ "$SNAPSHOT" != "" ]; then
VERSION="$VERSION~$SNAPSHOT";
fi;
DIRECTORY="zoneminder_$VERSION";
if [ -d "$DIRECTORY.orig" ]; then
echo "$DIRECTORY.orig already exists. Please delete it."
exit 0;
fi;
echo "Doing $TYPE release $DIRECTORY";
mv "${GITHUB_FORK}_zoneminder_release" "$DIRECTORY.orig";
if [ $? -ne 0 ]; then
echo "Error status code is: $?"
echo "Setting up build dir failed.";
exit $?;
fi;
cd "$DIRECTORY.orig";
# Init submodules
git submodule init
git submodule update --init --recursive
# Cleanup
rm -rf .git
rm .gitignore
cd ../
if [ -e "$DIRECTORY.orig.tar.gz" ]; then
read -p "$DIRECTORY.orig.tar.gz exists, overwrite it? [Y/n]"
if [[ "$REPLY" == "" || "$REPLY" == [yY] ]]; then
tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig
fi;
else
tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig
fi;
IFS=',' ;for DISTRO in `echo "$DISTROS"`; do
echo "Generating package for $DISTRO";
cd $DIRECTORY.orig
if [ -e "debian" ]; then
rm -rf debian
fi;
# Generate Changelog
if [ "$DISTRO" == "beowulf" ]; then
cp -Rpd distros/beowulf debian
else
cp -Rpd distros/ubuntu2004 debian
fi;
if [ "$DEBEMAIL" != "" ] && [ "$DEBFULLNAME" != "" ]; then
AUTHOR="$DEBFULLNAME <$DEBEMAIL>"
else
if [ -z `hostname -d` ] ; then
AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`.local>"
else
AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`>"
fi
fi
if [ "$URGENCY" = "" ]; then
URGENCY="medium"
fi;
if [ "$SNAPSHOT" == "stable" ]; then
cat <<EOF > debian/changelog
zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY
* Release $VERSION
-- $AUTHOR $DATE
EOF
cat <<EOF > debian/NEWS
zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY
* Release $VERSION
-- $AUTHOR $DATE
EOF
else
cat <<EOF > debian/changelog
zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY
*
-- $AUTHOR $DATE
EOF
cat <<EOF > debian/changelog
zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY
*
-- $AUTHOR $DATE
EOF
fi;
# Leave the .orig so that we don't pollute it when building deps
cd ..
if [ $TYPE == "binary" -o $TYPE == "binary-arch" ]; then
# Auto-install all ZoneMinder's dependencies using the Debian control file
sudo apt-get install devscripts equivs
sudo mk-build-deps -ir $DIRECTORY.orig/debian/control
echo "Status: $?"
# binary-arch skips Architecture: all packages. Used on secondary
# architectures so that the arch-independent .debs are only built and
# uploaded once, by the amd64 builders.
if [ $TYPE == "binary-arch" ]; then
DEBUILD="debuild -B -uc -us"
else
DEBUILD="debuild -b -uc -us"
fi;
else
if [ $TYPE == "local" ]; then
# Auto-install all ZoneMinder's dependencies using the Debian control file
sudo apt-get install devscripts equivs
sudo mk-build-deps -ir $DIRECTORY.orig/debian/control
echo "Status: $?"
DEBUILD="debuild -i -us -uc -b"
else
# Source build, don't need build depends.
DEBUILD="debuild -S -sa"
fi;
fi;
cd $DIRECTORY.orig
if [ "$DEBSIGN_KEYID" != "" ]; then
DEBUILD="$DEBUILD -k$DEBSIGN_KEYID"
fi
# Add any extra options specified on the CLI
DEBUILD="$DEBUILD $DEBBUILD_EXTRA"
eval $DEBUILD
if [ $? -ne 0 ]; then
echo "Error status code is: $?"
echo "Build failed.";
exit $?;
fi;
cd ../
if [ $TYPE == "binary" -o $TYPE == "binary-arch" ]; then
if [ "$INTERACTIVE" != "no" ]; then
read -p "Not doing dput since it's a binary release. Do you want to install it? (y/N)"
if [[ $REPLY == [yY] ]]; then
sudo dpkg -i $DIRECTORY*.deb
fi;
read -p "Do you want to upload this binary to zmrepo? (y/N)"
if [[ $REPLY == [yY] ]]; then
if [ "$RELEASE" != "" ]; then
echo "scp \"zoneminder_${VERSION}-${DISTRO}\"* \"zoneminder-doc_${VERSION}-${DISTRO}\"* \"zoneminder-dbg_${VERSION}-${DISTRO}\"* \"zoneminder_${VERSION}.orig.tar.gz\" \"zmrepo@zmrepo.zoneminder.com:debian/release-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}/mini-dinstall/incoming/\"";
scp "zoneminder_${VERSION}-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.zoneminder.com:debian/release-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}/mini-dinstall/incoming/"
else
if [ "$BRANCH" == "" ]; then
scp "zoneminder_${VERSION}-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.zoneminder.com:debian/master/mini-dinstall/incoming/"
else
scp "$DIRECTORY-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.zoneminder.com:debian/${BRANCH}/mini-dinstall/incoming/"
fi;
fi;
fi;
fi;
else
SC="zoneminder_${VERSION}-${DISTRO}${PACKAGE_VERSION}_source.changes";
if [ "$PPA" == "" ]; then
if [ "$RELEASE" != "" ]; then
# We need to use our official tarball for the original source, so grab it and overwrite our generated one.
if [ "${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" == "1.30" ]; then
PPA="ppa:iconnor/zoneminder-stable"
else
PPA="ppa:iconnor/zoneminder-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}"
fi;
else
if [ "$BRANCH" == "" ]; then
PPA="ppa:iconnor/zoneminder-master";
else
PPA="ppa:iconnor/zoneminder-$BRANCH";
fi;
fi;
fi;
dput="Y";
if [ "$INTERACTIVE" != "no" ]; then
read -p "Ready to dput $SC to $PPA ? Y/n...";
if [[ "$REPLY" == "" || "$REPLY" == [yY] ]]; then
dput -d $PPA $SC
fi;
else
if [ "$DPUT" != "no" ]; then
echo "dputting to $PPA";
dput -d $PPA $SC
fi;
fi;
fi;
done; # foreach distro
if [ "$INTERACTIVE" != "no" ]; then
read -p "Do you want to keep the checked out version of Zoneminder (in case you want to modify it later) [y/N]"
[[ $REPLY == [yY] ]] && { mv "$DIRECTORY.orig" zoneminder_release; echo "The checked out copy is preserved in zoneminder_release"; } || { rm -fr "$DIRECTORY.orig"; echo "The checked out copy has been deleted"; }
echo "Done!"
else
rm -fr "$DIRECTORY.orig"; echo "The checked out copy has been deleted";
fi