Add branded Finder icon for Probo Agent.app

The macOS URL-handler bundle had no CFBundleIconFile, so
Finder showed the generic app icon after PKG install. Keep a
single master PNG and generate AppIcon.icns at build time
with sips/iconutil, matching the auditor-mode approach.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
Ludovic Vielle
2026-07-24 20:06:57 +02:00
parent 002437b568
commit 2532b0bb6b
6 changed files with 79 additions and 3 deletions

View File

@@ -149,6 +149,59 @@ team_id_option() {
printf '"%s"' "${APPLE_TEAM_ID}"
}
# Build AppIcon.icns from the single committed master PNG
# (enroll-ui/Resources/icon-original.png), matching auditor-mode's
# pad-then-resize pipeline. Writes under STAGE; does not touch the
# source tree.
generate_app_icon_icns() {
local icon_original="$1"
local icns_out="$2"
local icon_dir padded tmp iconset
if [ ! -f "${icon_original}" ]; then
echo "error: missing app icon source ${icon_original}" >&2
exit 1
fi
icon_dir="${STAGE}/app-icon"
padded="${icon_dir}/icon-padded.png"
tmp="${icon_dir}/icon-padded.tmp.png"
iconset="${icon_dir}/AppIcon.iconset"
rm -rf "${icon_dir}"
mkdir -p "${iconset}"
# 10% padding on all sides (960 content inside 1200 canvas).
sips -z 960 960 "${icon_original}" --out "${tmp}" >/dev/null
sips --padToHeightWidth 1200 1200 "${tmp}" --out "${padded}" >/dev/null
rm -f "${tmp}"
# Write through temp names: sips mishandles @2x suffixes in --out paths.
sips -z 16 16 "${padded}" --out "${icon_dir}/16.png" >/dev/null
sips -z 32 32 "${padded}" --out "${icon_dir}/32.png" >/dev/null
sips -z 64 64 "${padded}" --out "${icon_dir}/64.png" >/dev/null
sips -z 128 128 "${padded}" --out "${icon_dir}/128.png" >/dev/null
sips -z 256 256 "${padded}" --out "${icon_dir}/256.png" >/dev/null
sips -z 512 512 "${padded}" --out "${icon_dir}/512.png" >/dev/null
sips -z 1024 1024 "${padded}" --out "${icon_dir}/1024.png" >/dev/null
# Build @2x names via concatenation so the shell never treats @ as a
# glob qualifier (and sips is never asked to write those paths).
local at2x
at2x='@2x.png'
cp "${icon_dir}/16.png" "${iconset}/icon_16x16.png"
cp "${icon_dir}/32.png" "${iconset}/icon_16x16${at2x}"
cp "${icon_dir}/32.png" "${iconset}/icon_32x32.png"
cp "${icon_dir}/64.png" "${iconset}/icon_32x32${at2x}"
cp "${icon_dir}/128.png" "${iconset}/icon_128x128.png"
cp "${icon_dir}/256.png" "${iconset}/icon_128x128${at2x}"
cp "${icon_dir}/256.png" "${iconset}/icon_256x256.png"
cp "${icon_dir}/512.png" "${iconset}/icon_256x256${at2x}"
cp "${icon_dir}/512.png" "${iconset}/icon_512x512.png"
cp "${icon_dir}/1024.png" "${iconset}/icon_512x512${at2x}"
iconutil -c icns "${iconset}" -o "${icns_out}"
}
# Build Probo Agent.app (URL handler + embedded privileged helper) into
# parent_dir. Signs nested Mach-Os then the .app bundle (bottom-up).
build_probo_agent_app() {
@@ -156,9 +209,9 @@ build_probo_agent_app() {
local build_dir render_dir
local helper_info_plist helper_launchd_plist
local helper_binary url_handler_binary bin_dir
local app_root contents macos launch_services launch_daemons
local app_root contents macos resources launch_services launch_daemons
local plist embedded_helper embedded_launchd
local helper_requirement
local helper_requirement app_icon_original app_icon_icns
local -a helper_linker_flags swift_arch_args
build_dir="${STAGE}/enroll-ui-build"
@@ -218,18 +271,24 @@ build_probo_agent_app() {
app_root="${parent_dir}/${APP_NAME}"
contents="${app_root}/Contents"
macos="${contents}/MacOS"
resources="${contents}/Resources"
launch_services="${contents}/Library/LaunchServices"
launch_daemons="${contents}/Library/LaunchDaemons"
plist="${contents}/Info.plist"
embedded_helper="${launch_services}/${HELPER_LABEL}"
embedded_launchd="${launch_daemons}/${HELPER_LABEL}.plist"
app_icon_original="${ENROLL_UI_DIR}/Resources/icon-original.png"
app_icon_icns="${STAGE}/AppIcon.icns"
rm -rf "${app_root}"
mkdir -p "${macos}" "${launch_services}" "${launch_daemons}"
mkdir -p "${macos}" "${resources}" "${launch_services}" "${launch_daemons}"
generate_app_icon_icns "${app_icon_original}" "${app_icon_icns}"
install -m 0755 "${url_handler_binary}" "${macos}/${URL_HANDLER_NAME}"
install -m 0755 "${helper_binary}" "${embedded_helper}"
install -m 0644 "${helper_launchd_plist}" "${embedded_launchd}"
ditto --norsrc --noextattr "${app_icon_icns}" "${resources}/AppIcon.icns"
# Sign helper before writing Info.plist so SMPrivilegedExecutables
# can embed the helper's designated requirement.

View File

@@ -4,3 +4,9 @@
# Rendered by build.sh from *.tmpl (do not commit)
Shared/*.generated.swift
# Generated from Resources/icon-original.png (build staging preferred;
# ignore if produced locally under enroll-ui).
Resources/AppIcon.icns
Resources/AppIcon.iconset/
Resources/icon-padded.png

View File

@@ -21,6 +21,8 @@
<string>Probo Agent</string>
<key>CFBundleDisplayName</key>
<string>Probo Agent</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

View File

@@ -64,6 +64,14 @@ then the app bundle, optionally signs the product with
profile; submits use `--keychain-profile` so the secret is not on
`notarytool submit` argv).
The Finder/Dock icon for `Probo Agent.app` comes from a single master
PNG, `cmd/probo-agent/installer/macos/enroll-ui/Resources/icon-original.png`
(Probo square mark). At PKG build time `build.sh` pads/resizes it with
`sips`, compiles `AppIcon.icns` with `iconutil` under the build stage
directory, and installs it into `Contents/Resources/`. `Info.plist`
sets `CFBundleIconFile` to `AppIcon`. Do not commit generated
`.icns` / iconset PNGs.
There is no unsigned PKG path and no osascript elevation fallback.
Local testing of browser enrollment requires a Developer ID–signed
build. CLI enrollment without the app uses `sudo probo-agent install`.

View File

@@ -51,6 +51,7 @@ func EqualNames(a, b string) bool {
// itself, not its apex.
func CheckNames(hostname string) ([]string, error) {
hostname = strings.ToLower(strings.TrimSpace(hostname))
hostname = strings.TrimSuffix(hostname, ".")
if hostname == "" {
return nil, fmt.Errorf("cannot build DNS check names: empty hostname")