Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/workflows/macos-package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Build MacOS community client pkg files

on:
push:
branches: [master]
pull_request:
branches: [master]

permissions:
contents: read
pull-requests: read

jobs:
macos_package:
name: Build a CFEngine community agent pkg file for MacOS
strategy:
matrix:
os: [macos-26-intel, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Together Action
uses: actions/checkout@v3
with:
repository: cfengine/together-javascript-action
ref: main
ssh-key: ${{ secrets.GH_ACTIONS_SSH_DEPLOY_KEY_TOGETHER_REPO }}
ssh-known-hosts: github.com

- name: Action step
uses: ./
id: together
with:
myToken: ${{ secrets.GITHUB_TOKEN }}

- name: Checkout Core
uses: actions/checkout@v3
with:
repository: cfengine/core
path: core
ref: ${{steps.together.outputs.core || github.base_ref}}
submodules: recursive

- name: Checkout Masterfiles
uses: actions/checkout@v3
with:
repository: cfengine/masterfiles
path: masterfiles
ref: ${{steps.together.outputs.masterfiles || github.base_ref}}

- name: Checkout Buildscripts
uses: actions/checkout@v3
with:
repository: cfengine/buildscripts
path: buildscripts
ref: ${{steps.together.outputs.buildscripts || github.base_ref}}
fetch-depth: 20

- name: Build package natively
id: build_package
run: |
./buildscripts/ci/build-macos.sh

- name: Save artifacts
uses: actions/upload-artifact@v4
with:
name: macos-package-artifact
path: /Users/runner/work/buildscripts/buildscripts/cfengine-community
13 changes: 10 additions & 3 deletions build-scripts/compile-options
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ solaris)
export CFLAGS
fi
;;
darwin)
# Apple's linker (ld64) doesn't understand the GNU/Solaris "-R" rpath
# spelling used in the default case below, it needs "-rpath" instead.
LDFLAGS="-L$BUILDPREFIX/lib -Wl,-rpath,$BUILDPREFIX/lib"
;;
*)
LDFLAGS="-L$BUILDPREFIX/lib -Wl,-R$BUILDPREFIX/lib"
;;
Expand Down Expand Up @@ -144,7 +149,7 @@ var_append DEPS "librsync" # Library for synchronization of file

# coreutils is only built for redhat/debian/windows for now
case "$OS_FAMILY" in
hpux | aix | solaris | freebsd) ;;
hpux | aix | solaris | freebsd | darwin) ;;
*)
var_append DEPS "coreutils" # Provides a standalone 'date' binary
;;
Expand All @@ -167,7 +172,7 @@ esac

# Non-exotics dependencies
case "$OS_FAMILY" in
hpux | aix | solaris | freebsd | mingw) ;;
hpux | aix | solaris | freebsd | mingw | darwin) ;;
*)
# Library for managing Extended Attributes (xattrs) on filesystems
var_append DEPS "libattr"
Expand Down Expand Up @@ -257,7 +262,9 @@ esac

# Determine whether or not to run tests
case "$OS_FAMILY" in
mingw | freebsd) TESTS=no ;;
# Test suites for the bundled dependencies aren't yet verified to pass on
# macOS, so play it safe like the other newer/less-trodden platforms.
mingw | freebsd | darwin) TESTS=no ;;
*) TESTS=all ;;
esac
export TESTS
Expand Down
21 changes: 20 additions & 1 deletion build-scripts/detect-environment
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ detect_os() {
Darwin)
OS_FAMILY=darwin
OS=darwin
OS_VERSION=$(sw_vers -productVersion)
;;
FreeBSD)
OS_FAMILY=freebsd
Expand Down Expand Up @@ -236,7 +237,15 @@ detect_distribution() {
# detected, then the "pkg-build-rpm" script will be called from the
# "install-dependencies" script.
detect_packaging() {
if [ -f /bin/rpm ]; then
if [ "$UNAME_S" = Darwin ]; then
# macOS has no package manager suitable for the iterative
# build-then-install-locally workflow the other DEP_PACKAGING values
# use (rpm/dpkg/pkg_add/etc all maintain a real system package
# database). pkg-build-macos/pkg-find-macos/pkg-install-macos use a
# plain tarball format with their own lightweight file-registry
# instead, see deps-packaging/pkg-install-macos.
DEP_PACKAGING=macos
elif [ -f /bin/rpm ]; then
DEP_PACKAGING=rpm
elif [ -f /usr/bin/dpkg ]; then
DEP_PACKAGING=deb
Expand All @@ -262,6 +271,9 @@ detect_packaging() {
mingw)
PACKAGING=msi
;;
darwin)
PACKAGING=macos
;;
*)
PACKAGING=$DEP_PACKAGING
;;
Expand Down Expand Up @@ -302,6 +314,10 @@ detect_arch() {
hpux)
ARCH=$UNAME_M
;;
macos)
# arm64 on Apple Silicon, x86_64 on Intel Macs.
ARCH=$UNAME_M
;;
*)
log_error "Unknown packaging system"
exit 42
Expand Down Expand Up @@ -373,6 +389,9 @@ detect_cores() {
hpux)
NUM_CORES="$(ioscan -k -C processor | grep -c processor)"
;;
darwin)
NUM_CORES="$(sysctl -n hw.ncpu)"
;;
*)
log_debug "Detected OS family is UNKNOWN, defaulting amount of CPU cores to 1"
NUM_CORES=1
Expand Down
29 changes: 28 additions & 1 deletion build-scripts/functions
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,27 @@ uninstall_freebsd_pkgs() {
fi
}

# uninstall_macos_pkgs: Remove macOS build-dependency packages matching a
# pattern. There's no system package manager suitable for our iterative
# build/install-locally workflow on macOS, so pkg-build-macos/pkg-install-macos
# track installed files themselves in a flat-file registry (see
# deps-packaging/pkg-install-macos) instead of using a real package database.
# Args:
# $1 - Package name pattern (regex)
uninstall_macos_pkgs() {
REGISTRY="$BASEDIR/.cfbuild-macos-db"
[ -d "$REGISTRY" ] || return 0
for filelist in "$REGISTRY"/*.filelist; do
[ -e "$filelist" ] || continue
name=$(basename "$filelist" .filelist)
echo "$name" | grep_q "^$1\$" || continue
while IFS= read -r f; do
rm -f "/$f"
done <"$filelist"
rm -f "$filelist"
done
}

# uninstall_cfbuild: Remove all cfbuild packages (runtime and devel)
# Uses the appropriate uninstall function based on $DEP_PACKAGING
uninstall_cfbuild() {
Expand All @@ -230,6 +251,7 @@ uninstall_cfbuild() {
solaris) uninstall_solaris_pkgs 'cfbuild-.*' ;;
freebsd) uninstall_freebsd_pkgs 'cfbuild-.*' ;;
hpux) uninstall_hpux_pkgs 'cfbuild-.*' ;;
macos) uninstall_macos_pkgs 'cfbuild-.*' ;;
*)
log_error "Unknown packaging system: $DEP_PACKAGING"
exit 1
Expand All @@ -243,6 +265,7 @@ uninstall_cfbuild_devel() {
case "$DEP_PACKAGING" in
rpm) uninstall_rpms 'cfbuild-.*-devel' ;;
deb) uninstall_debs 'cfbuild-.*-devel' ;;
macos) uninstall_macos_pkgs 'cfbuild-.*-devel' ;;
solaris) uninstall_solaris_pkgs 'cfbuild-.*-devel' ;;
freebsd) uninstall_freebsd_pkgs 'cfbuild-.*-devel' ;;
hpux) uninstall_hpux_pkgs 'cfbuild-.*-devel' ;;
Expand Down Expand Up @@ -650,6 +673,7 @@ func_sha256() {
case "$UNAME_S" in
SunOS) digest -a sha256 "$@" ;;
AIX) openssl dgst -sha256 "$@" | cut -d ' ' -f 2 ;;
Darwin) shasum -a 256 "$@" | cut -d ' ' -f 1 ;;
*) fatal "Can't find command for computing SHA-256" ;;
esac
fi
Expand Down Expand Up @@ -699,8 +723,11 @@ retry_wrapper() {
else
err_ret=$?
# in case say dpkg locks are held by automatic updates or something
# -P is a GNU grep extension (missing on e.g. macOS' BSD grep), and
# this is diagnostic-only, so use portable -E and never let it trip
# "set -e" when nothing matches.
# shellcheck disable=SC2009
ps -efl | grep -P '(apt|dpkg|yum|dnf|zypper|rpm|pkg)'
ps -ef | grep -E '(apt|dpkg|yum|dnf|zypper|rpm|pkg)' || true
maxtries=$((maxtries - 1))
echo "* FAILURE $err_ret"
echo "* Sleeping for: $pause seconds"
Expand Down
117 changes: 111 additions & 6 deletions build-scripts/package
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,14 @@ P="$BASEDIR/buildscripts/packaging/$PKG"
if [ "$BUILDPREFIX" != /var/cfengine ]; then
log_debug "Custom prefix detected: $BUILDPREFIX"
safe_prefix="$(echo "$BUILDPREFIX" | sed -e 's:/::g')"
file_to_patch=$P/../common/script-templates/$PACKAGING-script-common.sh
if [ -f "$file_to_patch" ]; then
# Update installation scripts to use custom prefix
log_debug "Patching script template: $file_to_patch"
sed "s:/var/cfengine:$BUILDPREFIX:" "$file_to_patch" >"$file_to_patch".new
mv "$file_to_patch".new "$file_to_patch"
source_script=$P/../common/script-templates/$PACKAGING-script-common.sh
patched_script=$P/../common/script-templates/patched-$PACKAGING-script-common.sh
if [ -f "$source_script" ]; then
# Patch installation scripts for custom prefix, create patched-$PACKAGING-script-common.sh which is not version tracked
log_debug "Patching from source: $source_script to patched script: $patched_script"
sed "s:/var/cfengine:$BUILDPREFIX:" "$source_script" >"$patched_script"
else
log_error "Source $PACKAGING-script-common.sh not available at $source_script"
fi
else
log_debug "Using default prefix: /var/cfengine"
Expand Down Expand Up @@ -488,6 +490,109 @@ hpux)
log_debug "Creating HP-UX depot package with swpackage"
/usr/sbin/swpackage -s "$BASEDIR/$PKG/$PKG-$VERSION$safe_prefix.psf" -x media_type=tape @ "$BASEDIR/$PKG/pkg/$NAME.depot"
;;
macos)
# macOS installer package (.pkg) creation.
log_debug "Creating macOS installer package for $PKG"

# Unlike rpm/deb (many small packages tied together by the package
# manager's dependency resolution: cfengine-nova.rpm Requires the
# separate cfbuild-openssl.rpm etc, all installed side by side), macOS
# has no repository to fetch declared package dependencies from, and the
# ask here was one self-contained .pkg -- so we build a single payload
# merging both halves of the build:
# - $BASEDIR/cfengine/dist: core/enterprise/nova/masterfiles' own "make
# install" output (see build-scripts/compile's DESTDIR=cfengine/dist)
# - $PREFIX: the bundled runtime dependencies (openssl, libcurl, lmdb,
# ...), which install-dependencies installs for real onto this build
# host's live prefix (see pkg-install-macos) so each dependency's own
# build can link/run against the previous one. uninstall_cfbuild_devel
# already stripped the -devel packages (headers, pkgconfig, static
# libs) above, so what's left here is exactly the runtime content we
# want to ship.
STAGE="$BASEDIR/$PKG/pkg"
rm -rf "$STAGE"
mkdir -p "$STAGE$PREFIX"
cp -a "$BASEDIR/cfengine/dist"/* "$STAGE/"
(cd "$PREFIX" && tar cf - .) | (cd "$STAGE$PREFIX" && tar xf -)

# configure always passes --with-init-script (there's no per-OS
# conditional for it), so core generates a Linux-style SysV init.d script
# regardless of platform. It's inert on macOS (nothing runs it, we use
# launchd instead, wired up below), so drop it rather than ship dead
# weight that looks like a broken install to anyone inspecting the .pkg.
rm -rf "$STAGE/etc/init.d" "$STAGE/etc/sysconfig" "$STAGE/etc/profile.d"

# Ship the LaunchDaemon plist templates as part of the payload so
# postinstall.sh (baked into the package, see the "darwin" case in
# packaging/common/cfengine-non-hub/postinstall.sh) can install them.
log_debug "Staging LaunchDaemon plists into $STAGE$PREFIX/misc/launchd"
mkdir -p "$STAGE$PREFIX/misc/launchd"
cp "$P/darwin"/*.plist "$STAGE$PREFIX/misc/launchd/"

# Generate the preinstall/postinstall scripts pkgbuild embeds in the
# package, and a standalone uninstall script: macOS installer packages
# have no built-in uninstall lifecycle, so preremove/postremove are
# shipped as a script an admin runs by hand instead.
SCRIPTSDIR="$BASEDIR/$PKG/scripts"
log_debug "Generating installer scripts in $SCRIPTSDIR"
rm -rf "$SCRIPTSDIR"
mkdir -p "$SCRIPTSDIR"
"$P/../common/produce-script" "$PKG" preinstall macos >"$SCRIPTSDIR/preinstall"
"$P/../common/produce-script" "$PKG" postinstall macos >"$SCRIPTSDIR/postinstall"

{
"$P/../common/produce-script" "$PKG" preremove macos
"$P/../common/produce-script" "$PKG" postremove macos
} >"$STAGE$PREFIX/bin/uninstall-cfengine.sh"

# produce-script's output starts with script-header.sh, which (like every
# other fragment) has no shebang: rpm/deb/solaris/hpux/aix all invoke
# their scriptlets via an explicit interpreter ("sh scriptfile"), so it
# never mattered there. installer(8) is different -- it execve()s
# preinstall/postinstall directly, and a script with no "#!" line fails
# immediately with ENOEXEC ("Exec format error"), which is exactly what
# aborted the real install attempt this was debugged from (see
# /var/log/install.log: "PackageKit: Error Domain=NSPOSIXErrorDomain
# Code=8 'Exec format error'" on ./preinstall). Prepend it here rather
# than in script-header.sh itself, to avoid touching every other
# platform's scriptlets for a need that's specific to this one.
for script in "$SCRIPTSDIR/preinstall" "$SCRIPTSDIR/postinstall" "$STAGE$PREFIX/bin/uninstall-cfengine.sh"; do
{ echo '#!/bin/sh'; cat "$script"; } >"$script.tmp"
mv "$script.tmp" "$script"
chmod +x "$script"
done

IDENTIFIER="com.cfengine.$PKG"

# Build a component package, then wrap it in a product archive (also a
# .pkg): productbuild's output is what Installer.app/installer(8) expect
# and is the form that can later gain a distribution.xml/welcome screen
# or get through "productsign" for notarization.
COMPONENT_PKG="$BASEDIR/$PKG/$PKG-component.pkg"
log_debug "Building component package with pkgbuild"
pkgbuild \
--root "$STAGE" \
--identifier "$IDENTIFIER" \
--version "$VERSION" \
--scripts "$SCRIPTSDIR" \
--install-location / \
"$COMPONENT_PKG"

if [ "$BUILD_TYPE" = RELEASE ]; then
NAME="$PKG-$VERSION.$RELEASE-macos$OS_VERSION_MAJOR-$ARCH.pkg"
else
NAME="$PKG-$VERSION-macos$OS_VERSION_MAJOR-$ARCH.pkg"
fi

log_debug "Building product package with productbuild: $NAME"
productbuild \
--package "$COMPONENT_PKG" \
"$BASEDIR/$PKG/$NAME"

# Unsigned: productsign/notarytool need a Developer ID Installer
# certificate and notarization credentials we don't have in this build
# environment. Sign and notarize as a separate step before distributing.
;;
msi)
# Windows MSI package creation
log_debug "Creating Windows MSI package for $PKG"
Expand Down
Loading
Loading