Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
729d28f
Replace Makefile releases with GoReleaser Pro.
davidnewhall Aug 27, 2026
12ed604
Skip Docker Hub login when DOCKERHUB_PASSWORD is unset.
cursoragent Aug 27, 2026
cac7413
Split Docker channels: nightly from main, unstable stays manual.
davidnewhall Aug 27, 2026
2ad8e64
Fix DMG extra_files for GoReleaser 2.18.
davidnewhall Aug 27, 2026
5e1a83f
Fix Darwin GoReleaser builds: enable CGO for energye/systray.
cursoragent Aug 27, 2026
12e53ee
Match Darwin min to Go 1.27 and skip Docker SBOM attestations.
davidnewhall Aug 27, 2026
a24cca7
Publish Homebrew as a cask, matching motifini.
davidnewhall Aug 27, 2026
1989e40
Fix packagecloud upload: drop duplicate armhf debs and unique nightly…
cursoragent Aug 27, 2026
09efe7d
Package linux armv7 only, and put REVISION on nFPM packages.
davidnewhall Aug 27, 2026
2d5c858
Fix packagecloud Debian parse error from untemplated nfpms.release.
cursoragent Aug 27, 2026
49ae941
Drop the channel name from nightly package versions.
davidnewhall Aug 27, 2026
fba938b
Pass REVISION into GoReleaser the same way CHANNEL is passed.
davidnewhall Aug 27, 2026
f1f4be4
Show the energye tray menu on click.
davidnewhall Aug 27, 2026
2ab0fe1
fix menu
davidnewhall Aug 27, 2026
ddb8182
Merge branch 'ci/goreleaser-pro' of github.com:Unpackerr/unpackerr in…
davidnewhall Aug 27, 2026
e0ef223
Merge branch 'main' into ci/goreleaser-pro
davidnewhall Aug 27, 2026
bf85345
Notarize the macOS DMG on a macOS runner and publish stable names to …
davidnewhall Aug 27, 2026
961f7b0
Fix release split: OIDC for Windows codesign, skip empty Apple secrets.
davidnewhall Aug 27, 2026
3bb7301
Decode Apple P12/p8 secrets as PEM or long-line base64 on macOS.
davidnewhall Aug 27, 2026
39452a4
Use GoReleaser .RawVersion in Info.plist instead of sprig trimSuffix.
davidnewhall Aug 28, 2026
6661645
Point macos_native ids at the app bundle and DMG.
davidnewhall Aug 28, 2026
55e6bae
Fail Darwin closed on missing Apple secrets and pin publisher Actions.
davidnewhall Aug 28, 2026
4686163
Name split jobs Build: OS and keep the workflows README current.
davidnewhall Aug 28, 2026
4005620
Upload Darwin dist as a single tar instead of the .app tree.
davidnewhall Aug 28, 2026
4219e23
Import GPG on the merge job so checksum signatures have a secret key.
davidnewhall Aug 28, 2026
c15182b
Read split dist/\$GOOS/artifacts.json when uploading unstable files.
davidnewhall Aug 28, 2026
7f8a348
Show the git revision in the merge job name (release 1054).
davidnewhall Aug 28, 2026
b784e22
Point the app bundle at GoReleaser's icon.icns and stop shipping a de…
davidnewhall Aug 28, 2026
0b4e3c5
Ship real FreeBSD pkgng .txz packages and fail closed when publish se…
davidnewhall Aug 28, 2026
32098a2
Advertise FreeBSD packages as ABI FreeBSD:*:<cpu> so 14/15 will pkg a…
davidnewhall Aug 28, 2026
26fa0d4
Set BuildDate and BuildUser on the AUR source build, matching the old…
davidnewhall Aug 28, 2026
8b1dd02
Put pkg config files in the top-level manifest array and restore Wind…
davidnewhall Aug 28, 2026
3df3425
Put README.html and docker-compose in the Linux tarball and AUR too.
davidnewhall Aug 28, 2026
3539a7a
Build FreeBSD .txz with fpm instead of a custom packer.
davidnewhall Aug 28, 2026
5607a74
Drop the embedded Python from the FreeBSD fpm wrapper.
davidnewhall Aug 28, 2026
37707e4
Fail unstable upload if any arch is missing, and default the workflow…
davidnewhall Aug 28, 2026
1bfd05f
Merge branch 'main' into ci/goreleaser-pro
davidnewhall Aug 28, 2026
c672e4d
Pin rsrc, keep Windows OIDC off the other split jobs, and mark the Fr…
davidnewhall Aug 28, 2026
e194791
Keep RPM armv7hl, and teach install.sh the 32-bit ARM name family.
davidnewhall Aug 28, 2026
4e7f763
Match Arch packages by path as well as name when signing.
davidnewhall Aug 28, 2026
d7eabb8
Ship the notarized Homebrew cask and record FreeBSD txz artifacts for…
davidnewhall Aug 28, 2026
e3564d8
Restore tagged package revisions, drop unused merge OIDC, and create …
davidnewhall Aug 28, 2026
390c0b6
Use conventional Linux package names and stop shipping ${PKG_RELEASE}…
davidnewhall Aug 28, 2026
08ae075
Drop the Python nFPM helper; insert tagged release with awk in the wo…
davidnewhall Aug 28, 2026
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
135 changes: 135 additions & 0 deletions .github/scripts/freebsd_txz.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/usr/bin/env bash
# After goreleaser --split, turn freebsd binaries into pkgng .txz (nFPM cannot).
# Same tool as v0.15.2: fpm -s dir -t freebsd. This only stages files, calls fpm,
# and lists the archives in artifacts.json for merge.
set -euo pipefail

DIST=${1:?usage: freebsd_txz.sh dist/freebsd}
REPO=$(cd "$(dirname "$0")/../.." && pwd)
DIST=$(cd "${DIST}" && pwd)
command -v fpm >/dev/null && command -v jq >/dev/null || { echo "need fpm and jq" >&2; exit 1; }

# fpm freebsd.rb runs `tar --transform` (GNU). Put gtar first on macOS.
if ! tar --version 2>/dev/null | grep -q 'GNU tar'; then
command -v gtar >/dev/null || { echo "fpm -t freebsd needs GNU tar" >&2; exit 1; }
_gnutar_bin=$(mktemp -d)
ln -s "$(command -v gtar)" "${_gnutar_bin}/tar"
export PATH="${_gnutar_bin}:${PATH}"
fi

artifacts=${DIST}/artifacts.json
VERSION=$(jq -r '.version // empty' "${DIST}/metadata.json")
if [[ -z ${VERSION} || ${VERSION} == unknown || ${VERSION} == unstable ]]; then
echo "refusing version '${VERSION}'" >&2
exit 1
fi
# Tagged pkgng version is 0.15.3_REVISION (same as old fpm). --nightly already
# puts REVISION in metadata.json (.Version).
if [[ ${VERSION} =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
[[ -n ${REVISION:-} ]] || { echo "REVISION required for tagged freebsd pkg" >&2; exit 1; }
VERSION="${VERSION}_${REVISION}"
fi

# goarch -> asset suffix / fpm -a / pkg ABI CPU (uname -p).
# fpm only knows amd64, i386, aarch64(→arm64). Unknown -a becomes getconf LONG_BIT
# (v0.15.2 i386+armhf are FreeBSD:13:64). We pass a known -a, then fix ARM CPU.
pkg_for() {
case $1 in
amd64) echo amd64 amd64 amd64 ;;
386) echo i386 i386 i386 ;;
arm) echo armhf amd64 armv7 ;;
arm64) echo arm64 aarch64 aarch64 ;;
*) echo "unsupported freebsd goarch $1" >&2; return 1 ;;
esac
}

CONF=/usr/local/etc/unpackerr/unpackerr.conf
# fpm --config-files is ignored by freebsd.rb (v0.15.2 had no config array).
# Unpack only when ABI or config still needs a patch.
fix_pkg() {
local pkg=$1 abi=$2 tmp list got cfg
got=$(tar -xJOf "${pkg}" +COMPACT_MANIFEST | jq -r .arch)
cfg=$(tar -xJOf "${pkg}" +MANIFEST | jq -r '.config[0] // empty')
[[ ${got} == "${abi}" && ${cfg} == "${CONF}" ]] && return
tmp=$(mktemp -d)
tar --transform 's|^/||' -xJf "${pkg}" -C "${tmp}"
jq --arg a "${abi}" '.arch=$a' "${tmp}/+COMPACT_MANIFEST" > "${tmp}/c.json"
jq --arg a "${abi}" --arg c "${CONF}" '.arch=$a | .config=[$c]' "${tmp}/+MANIFEST" > "${tmp}/m.json"
mv "${tmp}/c.json" "${tmp}/+COMPACT_MANIFEST"
mv "${tmp}/m.json" "${tmp}/+MANIFEST"
list=$(mktemp)
{ printf '%s\n' +COMPACT_MANIFEST +MANIFEST; (cd "${tmp}" && find usr -type f | sort); } > "${list}"
tar --owner=0 --group=0 --numeric-owner -Jcf "${pkg}" -C "${tmp}" \
--files-from "${list}" --transform 's|^\([^+]\)|/\1|'
rm -rf "${tmp}" "${list}"
}

stage() {
local bin=$1 root=$2 doc=${2}/usr/local/share/doc/unpackerr
mkdir -p "${root}/usr/local/bin" "${root}/usr/local/etc/unpackerr" \
"${root}/usr/local/etc/rc.d" "${root}/usr/local/share/man/man1" "${doc}"
install -m 755 "${bin}" "${root}/usr/local/bin/unpackerr"
install -m 755 "${REPO}/init/bsd/freebsd.rc.d" "${root}/usr/local/etc/rc.d/unpackerr"
install -m 644 "${REPO}/examples/unpackerr.conf.example" "${root}/usr/local/etc/unpackerr/unpackerr.conf"
install -m 644 "${REPO}/examples/unpackerr.conf.example" "${root}/usr/local/etc/unpackerr/unpackerr.conf.example"
[[ -f ${REPO}/unpackerr.1.gz ]] || { echo "missing unpackerr.1.gz" >&2; exit 1; }
install -m 644 "${REPO}/unpackerr.1.gz" "${root}/usr/local/share/man/man1/unpackerr.1.gz"
install -m 644 "${REPO}/LICENSE" "${doc}/LICENSE"
install -m 644 "${REPO}/examples/MANUAL.md" "${doc}/MANUAL.md"
install -m 644 "${REPO}/examples/MANUAL.html" "${doc}/unpackerr_manual.html"
install -m 644 "${REPO}/examples/docker-compose.yml" "${doc}/docker-compose.yml"
install -m 644 "${REPO}/examples/unpackerr.conf.example" "${doc}/unpackerr.conf.example"
[[ -f ${REPO}/README.html ]] && install -m 644 "${REPO}/README.html" "${doc}/README.html"
}

extra=$(mktemp)
echo '[]' > "${extra}"
seen=

while IFS='|' read -r goarch goarm path; do
[[ ${goarch} == arm && ${goarm} == 6 ]] && continue
[[ ${seen} == *"|${goarch}|"* ]] && continue
seen+="|${goarch}|"

bin=${path}
[[ -f ${bin} ]] || bin=${DIST}/${path}
[[ -f ${bin} ]] || { echo "freebsd binary missing: ${path}" >&2; exit 1; }

read -r suffix fpm_a cpu <<<"$(pkg_for "${goarch}")"
dest=${DIST}/unpackerr-${VERSION}.${suffix}.txz
tmp=$(mktemp -d)
stage "${bin}" "${tmp}"
rm -f "${dest}"
fpm -s dir -t freebsd --name unpackerr -v "${VERSION}" -a "${fpm_a}" \
--license MIT --url https://unpackerr.zip \
--maintainer "David Newhall II <captain at golift dot io>" \
--description "Extracts downloads so Radarr, Sonarr, Lidarr or Readarr may import them." \
--freebsd-origin https://github.com/Unpackerr/unpackerr --freebsd-osversion '*' \
--before-install "${REPO}/init/systemd/before-install.sh" \
--after-install "${REPO}/init/systemd/after-install.sh" \
--before-remove "${REPO}/init/systemd/before-remove.sh" \
--config-files "${CONF}" \
-C "${tmp}" -p "${dest}" .
Comment thread
golift-bot marked this conversation as resolved.
rm -rf "${tmp}"
fix_pkg "${dest}" "FreeBSD:*:${cpu}"
echo "wrote ${dest##*/}" >&2
# Merge unmarshals Type from internal_type (int), not type (string).
# UploadableArchive = 1. Path must be repo-relative so checksum/GitHub can open it.
relpath=${dest#"${REPO}/"}
jq --arg name "${dest##*/}" --arg path "${relpath}" --arg goarch "${goarch}" \
--arg goarm "${goarm}" --argjson itype 1 \
'. + [{name:$name, path:$path, goos:"freebsd", goarch:$goarch,
goarm: (if $goarm == "" then null else $goarm end),
type:"Archive", internal_type:$itype,
extra:{ID:"freebsd-pkg", Format:"txz", Ext:".txz"}}]' \
"${extra}" > "${extra}.n" && mv "${extra}.n" "${extra}"
done < <(jq -r '.[] | select(.type=="Binary" and .goos=="freebsd") | [.goarch, (.goarm // ""), .path] | join("|")' "${artifacts}")

for a in amd64 386 arm arm64; do
[[ ${seen} == *"|${a}|"* ]] || { echo "freebsd txz missing goarch ${a}" >&2; exit 1; }
done

jq --slurpfile extra "${extra}" '. + $extra[0]' "${artifacts}" > "${artifacts}.n"
mv "${artifacts}.n" "${artifacts}"
rm -f "${extra}"
echo "appended txz archives to ${artifacts}" >&2
90 changes: 90 additions & 0 deletions .github/scripts/macos_keychain.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env bash
# Import Developer ID + App Store Connect API key into a temporary keychain
# for GoReleaser Pro macos_native (codesign + notarytool).
set -euo pipefail

if [ -z "${MACOS_SIGN_P12:-}" ] || [ -z "${MACOS_SIGN_PASSWORD:-}" ]; then
echo "MACOS_SIGN_P12 / MACOS_SIGN_PASSWORD unset" >&2
exit 1
fi
if [ -z "${MACOS_NOTARY_KEY:-}" ] || [ -z "${MACOS_NOTARY_KEY_ID:-}" ] || [ -z "${MACOS_NOTARY_ISSUER_ID:-}" ]; then
echo "MACOS_NOTARY_KEY / MACOS_NOTARY_KEY_ID / MACOS_NOTARY_ISSUER_ID unset" >&2
exit 1
fi

tmp="${RUNNER_TEMP:-${TMPDIR:-/tmp}}"
cert="${tmp}/unpackerr.p12"
key="${tmp}/unpackerr.p8"
keychain="${tmp}/unpackerr.keychain-db"
profile="${MACOS_NOTARY_PROFILE_NAME:-unpackerr}"
password="${KEYCHAIN_PASSWORD:-$(openssl rand -base64 32)}"

# Quill accepted "path or base64". GitHub secrets are either PEM text or
# one-line base64. macOS openssl base64 -d without -A yields empty files
# for long lines, and PEM is not base64 at all.
write_secret() {
local dest=$1 envname=$2
python3 - "${dest}" "${envname}" <<'PY'
import base64, os, pathlib, sys

dest, envname = sys.argv[1], sys.argv[2]
raw = os.environ.get(envname, "")
if not raw.strip():
sys.stderr.write(f"{envname} empty\n")
sys.exit(1)
s = raw.strip().replace("\r", "")
if "BEGIN " in s:
data = (s if s.endswith("\n") else s + "\n").encode()
else:
compact = "".join(s.split())
compact += "=" * ((4 - len(compact) % 4) % 4)
try:
data = base64.b64decode(compact)
except Exception as exc:
sys.stderr.write(f"{envname} base64 decode failed: {exc}\n")
sys.exit(1)
if not data:
sys.stderr.write(f"{envname} decoded to empty ({len(raw)} input chars)\n")
sys.exit(1)
pathlib.Path(dest).write_bytes(data)
print(f"{envname}: {len(raw)} chars -> {len(data)} bytes")
PY
}

write_secret "${cert}" MACOS_SIGN_P12
write_secret "${key}" MACOS_NOTARY_KEY
chmod 600 "${cert}" "${key}"
echo "p12 $(wc -c < "${cert}" | tr -d ' ') bytes ($(file -b "${cert}"))"
echo "p8 $(wc -c < "${key}" | tr -d ' ') bytes ($(file -b "${key}"))"

security delete-keychain "${keychain}" 2>/dev/null || true
security create-keychain -p "${password}" "${keychain}"
security set-keychain-settings -lut 21600 "${keychain}"
security unlock-keychain -p "${password}" "${keychain}"
security import "${cert}" -P "${MACOS_SIGN_PASSWORD}" -A -t cert -f pkcs12 -k "${keychain}"
security set-key-partition-list -S apple-tool:,apple: -k "${password}" "${keychain}"
security list-keychain -d user -s "${keychain}"

identity="${MACOS_SIGN_IDENTITY:-}"
if [ -z "${identity}" ]; then
identity="$(security find-identity -v -p codesigning "${keychain}" | awk -F '"' '/Developer ID Application/{print $2; exit}')"
fi
if [ -z "${identity}" ]; then
echo "no Developer ID Application identity in ${keychain}" >&2
security find-identity -v -p codesigning "${keychain}" >&2 || true
exit 1
fi

xcrun notarytool store-credentials "${profile}" \
--key "${key}" \
--key-id "${MACOS_NOTARY_KEY_ID}" \
--issuer "${MACOS_NOTARY_ISSUER_ID}" \
--keychain "${keychain}"

{
echo "KEYCHAIN_PATH=${keychain}"
echo "MACOS_SIGN_IDENTITY=${identity}"
echo "MACOS_NOTARY_PROFILE_NAME=${profile}"
} >> "${GITHUB_ENV}"

echo "keychain ready: ${identity}"
69 changes: 69 additions & 0 deletions .github/scripts/macos_staple.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env bash
# Confirm Developer ID on Unpackerr.app, then staple the notarized DMG.
# codesign -dv writes to stderr; grep -q in a pipefail pipeline is a false fail
# (tee gets SIGPIPE after the first match). Dump to a file, then grep.
set -euo pipefail

root="${1:-dist/darwin}"
dump="${RUNNER_TEMP:-/tmp}/codesign-app.txt"

app="${root}/apps/unpackerr-app_darwinall/Unpackerr.app"
if [ ! -d "${app}" ]; then
app=""
while IFS= read -r p; do
case "${p}" in
*/dmg/*) continue ;;
esac
app="${p}"
break
done < <(find "${root}" -type d -name 'Unpackerr.app')
fi
if [ ! -d "${app}" ]; then
while IFS= read -r p; do
app="${p}"
break
done < <(find "${root}" -type d -name 'Unpackerr.app')
fi
if [ ! -d "${app}" ]; then
echo "Unpackerr.app missing under ${root}" >&2
find "${root}" -type f >&2 || true
exit 1
fi

codesign --verify --deep --strict "${app}"
codesign -dv --verbose=2 "${app}" >"${dump}" 2>&1
if ! grep -F "Developer ID Application" "${dump}" >/dev/null; then
echo "Unpackerr.app is not Developer ID signed; macos_native skipped or failed" >&2
cat "${dump}" >&2
exit 1
fi
echo "signed ${app}"
cat "${dump}"

found=0
while IFS= read -r dmg; do
[ -n "${dmg}" ] || continue
found=1
echo "stapling ${dmg}"
ok=0
for attempt in 1 2 3 4 5; do
if xcrun stapler staple "${dmg}"; then
ok=1
break
fi
if [ "${attempt}" -eq 5 ]; then
break
fi
echo "stapler attempt ${attempt} failed; waiting for Apple ticket"
sleep 20
done
if [ "${ok}" -ne 1 ]; then
exit 1
fi
done < <(find "${root}" -type f -name '*.dmg' | sort)

if [ "${found}" -eq 0 ]; then
echo "no DMG under ${root}" >&2
find "${root}" -type f >&2 || true
exit 1
fi
55 changes: 55 additions & 0 deletions .github/scripts/require_secrets.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
# Fail closed when a publishing secret is empty. GitHub maps missing secrets
# to "" so `if: secrets.FOO != ''` is not a substitute.
set -euo pipefail

CHANNEL="${CHANNEL:-}"
if [ -z "${CHANNEL}" ]; then
echo "CHANNEL is empty" >&2
exit 1
fi

missing=0
need() {
local name=$1
local val=${!name-}
if [ -z "${val}" ]; then
echo "missing secret: ${name}" >&2
missing=1
fi
}

need GORELEASER_PRO_KEY
need GPG_SIGNING_KEY
# Hub is part of every channel (GHCR + golift/unpackerr). Absence used to
# skip login and still go green; that is no longer allowed.
need DOCKERHUB_PASSWORD
Comment thread
golift-bot marked this conversation as resolved.
need CODESIGN_URL
need CODESIGN_CLIENT_CERT
need CODESIGN_CLIENT_KEY

# Nightly is Docker-only (no Darwin, no GitHub Release, no brew/AUR/packagecloud).
if [ "${CHANNEL}" != nightly ]; then
need MACOS_SIGN_P12
need MACOS_SIGN_PASSWORD
need MACOS_NOTARY_KEY
need MACOS_NOTARY_KEY_ID
need MACOS_NOTARY_ISSUER_ID
need PACKAGECLOUD_TOKEN
fi

if [ "${CHANNEL}" = release ]; then
need HOMEBREW_TAP_GITHUB_TOKEN
need AUR_DEPLOY_KEY
fi

if [ "${CHANNEL}" = unstable ]; then
need UNSTABLE_UPLOAD_KEY
fi

if [ "${missing}" -ne 0 ]; then
echo "refusing to publish CHANNEL=${CHANNEL} with empty secrets" >&2
exit 1
fi

echo "CHANNEL=${CHANNEL}: required secrets are present"
Loading