diff --git a/.github/scripts/freebsd_txz.sh b/.github/scripts/freebsd_txz.sh new file mode 100755 index 00000000..7a321d3d --- /dev/null +++ b/.github/scripts/freebsd_txz.sh @@ -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 " \ + --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}" . + 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 diff --git a/.github/scripts/macos_keychain.sh b/.github/scripts/macos_keychain.sh new file mode 100644 index 00000000..c22e6eed --- /dev/null +++ b/.github/scripts/macos_keychain.sh @@ -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}" diff --git a/.github/scripts/macos_staple.sh b/.github/scripts/macos_staple.sh new file mode 100644 index 00000000..94038145 --- /dev/null +++ b/.github/scripts/macos_staple.sh @@ -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 diff --git a/.github/scripts/require_secrets.sh b/.github/scripts/require_secrets.sh new file mode 100755 index 00000000..361ea9fd --- /dev/null +++ b/.github/scripts/require_secrets.sh @@ -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 +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" diff --git a/.github/scripts/unstable_upload.sh b/.github/scripts/unstable_upload.sh new file mode 100755 index 00000000..bf06bc20 --- /dev/null +++ b/.github/scripts/unstable_upload.sh @@ -0,0 +1,232 @@ +#!/usr/bin/env bash +# Publish auto-update artifacts to unstable.golift.io with stable names. +# +# GoReleaser writes versioned archives (unpackerr_0.15.3-1037_linux_amd64.tar.gz). +# Auto-update URLs cannot include that version; it lives in a sibling .txt. +# The payload is a gzipped (or zipped) *binary*, matching the old Makefile +# `gzip -9r` / `zip … $exe` layout — not a tar.gz (gunzip of a tar is a tar). +# +# Unpackerr.dmg +# unpackerr.amd64.exe.zip +# unpackerr.{amd64,386,arm,arm64}.linux.gz +# unpackerr.{amd64,i386,armhf,arm64}.freebsd.gz +# +# Sidecar: plain VERSION-REVISION (same as the pre-GoReleaser workflow). +set -euo pipefail + +if ! command -v jq >/dev/null; then + echo "jq is required to read artifacts.json" >&2 + exit 1 +fi + +dir="${1:-dist}" +artifacts="${dir}/artifacts.json" +metadata="${dir}/metadata.json" +combined="" + +# Split/merge writes dist/$GOOS/artifacts.json. A single-job release writes dist/artifacts.json. +if [ ! -f "${artifacts}" ]; then + shopt -s nullglob + parts=("${dir}"/*/artifacts.json) + shopt -u nullglob + if [ ${#parts[@]} -eq 0 ]; then + echo "missing ${artifacts} and ${dir}/*/artifacts.json" >&2 + find "${dir}" -name artifacts.json -o -name metadata.json >&2 || true + exit 1 + fi + combined="$(mktemp "${TMPDIR:-/tmp}/unpackerr-artifacts.XXXXXX")" + jq -s 'add' "${parts[@]}" > "${combined}" + artifacts="${combined}" + echo "merged ${#parts[@]} split artifacts.json files" +fi +if [ ! -f "${metadata}" ]; then + shopt -s nullglob + metas=("${dir}"/*/metadata.json) + shopt -u nullglob + if [ ${#metas[@]} -gt 0 ]; then + metadata="${metas[0]}" + fi +fi + +version="${VERSION:-}" +if [ -z "${version}" ] && [ -f "${metadata}" ]; then + version="$(jq -r '.version // empty' "${metadata}")" +fi +if [ -z "${version}" ] || [ "${version}" = "unknown" ] || [ "${version}" = "unstable" ]; then + echo "refusing to upload with VERSION=${version:-} (need dist/metadata.json or VERSION=0.15.3-1234)" >&2 + exit 1 +fi + +stage="${UNSTABLE_STAGE_DIR:-}" +owned_stage=0 +if [ -z "${stage}" ]; then + stage="$(mktemp -d "${TMPDIR:-/tmp}/unpackerr-unstable.XXXXXX")" + owned_stage=1 +fi +if [ "${owned_stage}" -eq 1 ]; then + trap 'rm -rf "${stage}"; [ -n "${combined}" ] && rm -f "${combined}"' EXIT +elif [ -n "${combined}" ]; then + trap 'rm -f "${combined}"' EXIT +fi +mkdir -p "${stage}" + +resolve_path() { + local p=$1 + if [ -f "${p}" ]; then + printf '%s' "${p}" + return + fi + if [ -f "${dir}/${p}" ]; then + printf '%s' "${dir}/${p}" + return + fi + echo "artifact missing: ${p}" >&2 + return 1 +} + +# Historical names: linux uses GOARCH; freebsd 386/arm used i386/armhf. +dest_name() { + local os=$1 arch=$2 + case "${os}" in + windows) + printf 'unpackerr.%s.exe.zip' "${arch}" + ;; + linux) + printf 'unpackerr.%s.linux.gz' "${arch}" + ;; + freebsd) + case "${arch}" in + 386) printf 'unpackerr.i386.freebsd.gz' ;; + arm) printf 'unpackerr.armhf.freebsd.gz' ;; + *) printf 'unpackerr.%s.freebsd.gz' "${arch}" ;; + esac + ;; + *) + return 1 + ;; + esac +} + +zip_exe() { + local src=$1 dest=$2 + python3 - "${src}" "${dest}" <<'PY' +import sys, zipfile +src, dest = sys.argv[1], sys.argv[2] +with zipfile.ZipFile(dest, "w", compression=zipfile.ZIP_DEFLATED) as zf: + zf.write(src, arcname="unpackerr.exe") +PY +} + +# Prefer GOARM 7 over 6 when both exist (same dest name). +while IFS=$'\t' read -r os arch goarm path; do + [ -n "${path}" ] || continue + if [ "${arch}" = arm ] && [ "${goarm}" = 6 ]; then + continue + fi + src="$(resolve_path "${path}")" || exit 1 + dest="$(dest_name "${os}" "${arch}")" || continue + out="${stage}/${dest}" + case "${os}" in + windows) + zip_exe "${src}" "${out}" "${out}" &2 +else + echo "Unpackerr.dmg missing from ${dir}; darwin split/notarize did not produce a DMG" >&2 + exit 1 +fi + +# Stable URLs keep the previous file if we skip an arch. Require the full set. +required=( + unpackerr.amd64.exe.zip + unpackerr.amd64.linux.gz + unpackerr.386.linux.gz + unpackerr.arm.linux.gz + unpackerr.arm64.linux.gz + unpackerr.amd64.freebsd.gz + unpackerr.i386.freebsd.gz + unpackerr.armhf.freebsd.gz + unpackerr.arm64.freebsd.gz +) +if [ "${CHANNEL:-}" != nightly ]; then + required+=(Unpackerr.dmg) +fi +missing=() +for name in "${required[@]}"; do + [ -f "${stage}/${name}" ] || missing+=("${name}") +done +if [ ${#missing[@]} -ne 0 ]; then + echo "unstable upload missing: ${missing[*]}" >&2 + ls -la "${stage}" >&2 || true + exit 1 +fi +shopt -s nullglob +staged=("${stage}"/*) +shopt -u nullglob + +upload() { + local file=$1 + local name + name="$(basename "${file}")" + echo "Uploading ${name} (${version})" + curl -sS --fail-with-body --retry 5 --retry-all-errors --retry-delay 2 \ + -H "X-API-KEY: ${UNSTABLE_UPLOAD_KEY}" \ + "https://unstable.golift.io/upload.php?folder=unpackerr" \ + -F "file=@${file};filename=${name}" + curl -sS --fail-with-body --retry 5 --retry-all-errors --retry-delay 2 \ + -H "X-API-KEY: ${UNSTABLE_UPLOAD_KEY}" \ + "https://unstable.golift.io/upload.php?folder=unpackerr" \ + -F "file=${version};filename=${name}.txt;type=text/plain" +} + +if [ -z "${UNSTABLE_UPLOAD_KEY:-}" ]; then + if [ -n "${GITHUB_ACTIONS:-}" ]; then + echo "UNSTABLE_UPLOAD_KEY unset; refusing to skip unstable.golift.io upload in CI" >&2 + exit 1 + fi + echo "UNSTABLE_UPLOAD_KEY unset; staged without uploading:" >&2 + ls -l "${stage}" + exit 0 +fi + +for file in "${staged[@]}"; do + [ -f "${file}" ] || continue + upload "${file}" +done diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 00000000..780f4f2e --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,92 @@ +# GitHub Actions + +Two workflows. `test-and-lint` (`codetests.yml`) runs tests and golangci-lint on push and `pull_request_target`. `build-and-release` (`release.yml`) is the only publisher. + +## Channels + +`release.yml` maps the GitHub event to a `CHANNEL` env that `.goreleaser.yaml` reads (`dockers_v2.disable`, packagecloud repo, unstable upload). `--nightly` is a GoReleaser flag: it bumps the version and turns off GitHub Releases / brew / AUR. + +| Trigger | CHANNEL | GoReleaser extra | What it publishes | +|---|---|---|---| +| Push tag `v*` | `release` | (none) | GitHub Release, Docker `:latest` + version tags, Homebrew cask, AUR, packagecloud `golift/pkgs` | +| Push branch `unstable` | `unstable` | `--nightly` | Docker `:unstable`, packagecloud `golift/unstable`, [unstable.golift.io](https://unstable.golift.io/?dir=unpackerr) | +| Cron `27 12 * * *` UTC, or `workflow_dispatch` on `main` | `nightly` | `--nightly` | Docker `:nightly` only | + +`workflow_dispatch` on any other ref is refused. Tagged and unstable publishes are **push** only. + +`unstable` is a **manual publish branch**. Recut it by pushing the commit you want: + +```bash +git push unpackerr ci/goreleaser-pro:unstable +``` + +Do not fast-forward `unstable` from `main` in CI. Calendar nightly builds `main` and does not touch the git `unstable` branch. + +Nightly skips the Darwin job. Apple notarization is slow and unused for a Docker-only cut. + +## Split, then merge + +GoReleaser Pro `--split` / `--continue --merge` builds each GOOS in its own job, then one merge job publishes. That exists because: + +- Darwin needs **CGO** (`energye/systray` Cocoa) and **native** `codesign` / `notarytool`. Quill on Linux can sign a naked binary; a `.app` inside a DMG is rejected by Gatekeeper unless the bundle is signed on macOS. +- Windows Authenticode talks to house **signerd** (`golift.io/codesign`) and needs `id-token: write` for GitHub OIDC. That is ubuntu, not macOS. +- Linux nFPM (deb/rpm) needs `rpm` + GPG. FreeBSD pkgng `.txz` is built after `--split` by `fpm -t freebsd` (nFPM has no freebsd target). Wrapper: `.github/scripts/freebsd_txz.sh`. + +So: + +1. **channel** — compute `CHANNEL`, extra args, and `REVISION` (`git rev-list --count --all`). This is the only place the count is taken; later jobs pass `needs.channel.outputs.revision`. +2. **require secrets** — fail closed if any signing/upload/push secret needed for that channel is empty. Missing secrets used to skip Docker Hub, Windows Authenticode, or unstable.golift.io and still go green. +3. **Build: linux / freebsd** (`split` on ubuntu) and **Build: windows** (own job: OIDC + signerd certs stay off the other legs) — `release --clean --split`. Filter with **`GGOOS`**, not `GOOS`. `GOOS` leaks into `go run` before-hooks (man pages, rsrc) and they then target the wrong OS. FreeBSD then runs `freebsd_txz.sh` (`fpm -t freebsd`). +4. **Build: darwin** (`split-darwin` on macos-latest, skipped on nightly) — import Developer ID + App Store Connect key, same `--split` with `GGOOS=darwin`, staple the DMG. +5. **release N** — download `dist-*` artifacts, import GPG (checksum signatures are created at merge, not split), `continue --merge`. Display name is `release` plus that `REVISION`. This is the only job that pushes Docker / GitHub / brew / AUR / packagecloud / unstable.golift.io. + +`REVISION` must be in the goreleaser-action `env:` map (Actions does not automatically forward `GITHUB_ENV` into a later step’s `env:` block). On `--nightly`, `nightly.version_template` already bakes it into `{{ .Version }}` (example `0.15.3-1056`), so man-page hooks use `{{ .Version }}` only — do not append `REVISION` again. The env var is still required: that template *creates* `.Version`, and tagged builds keep `.Version` as the semver while ldflags `Revision` / Darwin `CFBundleVersion` still need the count. + +nFPM `release` is not templated (GoReleaser copies it verbatim). `${PKG_RELEASE}` stayed literal and Packagecloud rejected `Version: 0.15.3~1081-${PKG_RELEASE}`. The tagged linux split inserts `release: REVISION` after the `nfpm-release:` marker; `--nightly` leaves it unset. Package files use `{{ replace "~" "-" .ConventionalFileName }}` (`unpackerr_0.15.2-960_i386.deb`, not `_linux_386`). Do not put `CHANNEL` in the package version. Do not set nFPM `version_metadata`. Tagged FreeBSD packages are `unpackerr-0.15.3_REVISION.amd64.txz`. + +## Darwin signing + +`.github/scripts/macos_keychain.sh` is **required** on Build: darwin. Missing `MACOS_SIGN_*` / `MACOS_NOTARY_*` fails the job; there is no unsigned-DMG fallback. + +`notarize.macos_native.ids` must be the **app bundle** and **DMG** ids (`unpackerr-app`, `unpackerr-dmg`), not the Darwin build id. Those pipes match Extra.ID on the `.app` / `.dmg`. After GoReleaser, `.github/scripts/macos_staple.sh` checks Developer ID on `Unpackerr.app` and staples the DMG (CloudKit can lag a bit after `notarytool` says Accepted). The Darwin `dist/` is packed into one tar before `upload-artifact`; uploading the `.app` tree on macos-latest hangs. + +## Merge destinations + +- **Docker** — always `ghcr.io/unpackerr/unpackerr` and Hub `docker.io/golift/unpackerr` (`DOCKERHUB_PUBLISH=1`). Empty `DOCKERHUB_PASSWORD` fails the merge job. Platforms: `linux/amd64`, `linux/arm64`, `linux/arm/v7`. +- **GitHub Release** — tagged `v*` only (`release.disable: "{{ .IsNightly }}"`). Windows assets are `unpackerr.amd64.exe.zip`. FreeBSD assets are pkgng `unpackerr-.{amd64,i386,armhf,arm64}.txz`. +- **Homebrew** — notarized `Unpackerr.app` from the `unpackerr-dmg` DMG → `golift/homebrew-mugs` `Casks/`. Skip on `--nightly` (and that channel has no Darwin job). +- **AUR** — `aur_sources` over SSH. Skip on `--nightly`. +- **packagecloud** — `golift/pkgs` vs `golift/unstable`. Skip when `CHANNEL=nightly`. +- **unstable.golift.io** — only `CHANNEL=unstable`. Auto-update URLs are **stable names**; version lives in a sibling `.txt` (plain `0.15.3-1056`, not JSON). Payload is a gzipped/zipped **binary**, not the versioned `tar.gz`. Script: `.github/scripts/unstable_upload.sh` (reads `dist/$GOOS/artifacts.json` after split/merge). Upload overwrites by name. Empty `UNSTABLE_UPLOAD_KEY` fails in GitHub Actions. + +| Stable name | Payload | +|---|---| +| `Unpackerr.dmg` | notarized universal DMG | +| `unpackerr.amd64.exe.zip` | Windows exe zip | +| `unpackerr.{amd64,386,arm,arm64}.linux.gz` | gzipped binary | +| `unpackerr.{amd64,i386,armhf,arm64}.freebsd.gz` | gzipped binary | + +Linux nFPM names are conventional (`unpackerr_0.15.3-1081_amd64.deb`, `unpackerr-0.15.3-1081.x86_64.rpm`). Arches: amd64, arm64, i386, armv7 (one `armhf` / RPM `armv7hl`). Darwin min macOS 13. Windows is `-H=windowsgui`. Empty `CODESIGN_URL` fails in GitHub Actions (local snapshots still skip). + +## Secrets + +Set on the `Unpackerr/unpackerr` repo (or org, granted to this public repo). `.github/scripts/require_secrets.sh` runs before any build job and **fails the workflow** if a secret required for that `CHANNEL` is empty. Nightly does not require Apple / Homebrew / AUR / packagecloud / unstable-upload secrets (those destinations are skipped). + +| Secret | Used by | +|---|---| +| `GORELEASER_PRO_KEY` | every goreleaser-action | +| `GPG_SIGNING_KEY` | Linux nFPM signatures | +| `MACOS_SIGN_P12`, `MACOS_SIGN_PASSWORD` | Developer ID `.p12` (PEM or long-line base64) | +| `MACOS_NOTARY_KEY`, `MACOS_NOTARY_KEY_ID`, `MACOS_NOTARY_ISSUER_ID` | App Store Connect `.p8` | +| `CODESIGN_URL`, `CODESIGN_CLIENT_CERT`, `CODESIGN_CLIENT_KEY` | Windows Authenticode (OIDC + mTLS) | +| `DOCKERHUB_PASSWORD` | Hub login (required) | +| `HOMEBREW_TAP_GITHUB_TOKEN` | `golift/homebrew-mugs` (release channel) | +| `PACKAGECLOUD_TOKEN` | `golift/pkgs` / `golift/unstable` | +| `AUR_DEPLOY_KEY` | AUR `unpackerr` (release channel) | +| `UNSTABLE_UPLOAD_KEY` | unstable.golift.io (unstable channel) | + +`GITHUB_TOKEN` is the default Actions token (GHCR + GitHub Releases). + +## Action pins + +`release.yml` pins `owner/repo@ # vX.Y.Z`. Floating major tags (`@v4`) are not used there. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 73d87324..f1241a55 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,294 +1,409 @@ name: build-and-release on: + # Calendar nightly is Docker :nightly from main. It does not touch the unstable branch. + schedule: + - cron: "27 12 * * *" + workflow_dispatch: push: branches: - unstable tags: - v* - pull_request: - branches: - - main +# Jobs that publish (GitHub Release, GHCR) set write on the job. The rest only checkout. permissions: contents: read + +# Darwin App/DMG notarization needs xcrun/codesign (macOS). Linux/Windows/FreeBSD +# split on ubuntu; merge publishes Docker, GitHub, Homebrew, AUR, packagecloud. +# See README.md in this folder. jobs: - macapp-test: - # description: "Builds and signs a macOS app then packages it in a notarized DMG." - if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/unstable' - name: Make macOS DMG - runs-on: macos-latest + channel: + runs-on: ubuntu-latest + outputs: + channel: ${{ steps.channel.outputs.channel }} + extra: ${{ steps.channel.outputs.extra }} + revision: ${{ steps.revision.outputs.revision }} steps: - - uses: actions/checkout@v7 - with: - # we need the whole thing so we can count commits. - fetch-depth: '0' - - uses: actions/setup-go@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - go-version-file: 'go.mod' - - name: go-generate - run: go generate ./... - - name: make-signdmg + fetch-depth: 0 + ref: ${{ github.event_name == 'schedule' && 'refs/heads/main' || github.ref }} + - name: Count git revisions + id: revision + run: echo "revision=$(git rev-list --count --all || echo 0)" >> "${GITHUB_OUTPUT}" + - name: Choose release channel + id: channel env: - APPLE_SIGNING_KEY: ${{ secrets.APPLE_SIGNING_KEY }} - AC_USERNAME: ${{ secrets.AC_USERNAME }} - AC_PASSWORD: ${{ secrets.AC_PASSWORD }} - AC_PROVIDER: ${{ secrets.AC_PROVIDER }} - id: release + REVISION: ${{ steps.revision.outputs.revision }} run: | - brew install Bearer/tap/gon - make signdmg - - name: upload artifacts - uses: actions/upload-artifact@v7 + set -euo pipefail + extra= + if [[ "${GITHUB_EVENT_NAME}" == schedule ]]; then + channel=nightly + extra=--nightly + elif [[ "${GITHUB_EVENT_NAME}" == workflow_dispatch && "${GITHUB_REF}" == refs/heads/main ]]; then + channel=nightly + extra=--nightly + elif [[ "${GITHUB_EVENT_NAME}" == push && "${GITHUB_REF}" == refs/tags/v* ]]; then + channel=release + elif [[ "${GITHUB_EVENT_NAME}" == push && "${GITHUB_REF}" == refs/heads/unstable ]]; then + channel=unstable + extra=--nightly + else + echo "refusing to release from ${GITHUB_REF} (${GITHUB_EVENT_NAME})" >&2 + exit 1 + fi + echo "channel=${channel}" >> "${GITHUB_OUTPUT}" + echo "extra=${extra}" >> "${GITHUB_OUTPUT}" + echo "CHANNEL=${channel} extra=${extra}" + + require-secrets: + name: require secrets + needs: channel + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - name: dmg-release - path: release + fetch-depth: 1 + ref: ${{ github.event_name == 'schedule' && 'refs/heads/main' || github.ref }} + - name: Fail if publishing secrets are missing + env: + CHANNEL: ${{ needs.channel.outputs.channel }} + GORELEASER_PRO_KEY: ${{ secrets.GORELEASER_PRO_KEY }} + GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} + DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} + CODESIGN_URL: ${{ secrets.CODESIGN_URL }} + CODESIGN_CLIENT_CERT: ${{ secrets.CODESIGN_CLIENT_CERT }} + CODESIGN_CLIENT_KEY: ${{ secrets.CODESIGN_CLIENT_KEY }} + MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }} + MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }} + MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }} + MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }} + MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} + PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }} + HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} + AUR_DEPLOY_KEY: ${{ secrets.AUR_DEPLOY_KEY }} + UNSTABLE_UPLOAD_KEY: ${{ secrets.UNSTABLE_UPLOAD_KEY }} + run: bash .github/scripts/require_secrets.sh - release-test: - # description: "Builds all the application client binaries and packages for a release." - if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/unstable' || github.ref == 'refs/heads/main' - outputs: - version: ${{ steps.release.outputs.version }} - name: Make Release Assets + split: + name: "Build: ${{ matrix.goos }}" + needs: [channel, require-secrets] + strategy: + fail-fast: false + matrix: + goos: [linux, freebsd] runs-on: ubuntu-latest permissions: contents: read - id-token: write - env: - GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} - # Map first: secrets cannot be referenced in steps.if. - CODESIGN_URL: ${{ secrets.CODESIGN_URL }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - # we need the whole thing so we can count commits. - fetch-depth: '0' - - uses: actions/setup-go@v7 + fetch-depth: 0 + ref: ${{ github.event_name == 'schedule' && 'refs/heads/main' || github.ref }} + - run: git fetch --force --tags + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: - go-version-file: 'go.mod' - - name: make-release - id: release - env: - # Job-level CODESIGN_URL is only for the codesign step's if. - # Make must not see it: signexe.sh would fail before the Action installs the CLI. - CODESIGN_URL: "" - run: | - sudo apt install -y rpm fakeroot zip debsigs gnupg jq libarchive-tools - sudo gem install --no-document fpm - echo "${GPG_SIGNING_KEY}" | gpg --import - - make release WINDOWS_ZIP=0 - source settings.sh ; echo "version=${VERSION}-${ITERATION}" >> $GITHUB_OUTPUT - - uses: golift/codesign@v1 - if: env.CODESIGN_URL != '' + go-version-file: go.mod + cache: true + - name: Install package tooling + if: matrix.goos == 'linux' + run: sudo apt-get update && sudo apt-get install -y rpm + - name: Install fpm + if: matrix.goos == 'freebsd' + run: sudo gem install --no-document fpm + - name: Import GPG key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0 with: - files: unpackerr.amd64.exe - url: ${{ secrets.CODESIGN_URL }} - client-cert: ${{ secrets.CODESIGN_CLIENT_CERT }} - client-key: ${{ secrets.CODESIGN_CLIENT_KEY }} - name: Unpackerr - website: https://unpackerr.zip - - name: Zip Windows exe + gpg_private_key: ${{ secrets.GPG_SIGNING_KEY }} + - name: Create GPG_SIGNING_KEY file + env: + GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} + run: printf '%s\n' "${GPG_SIGNING_KEY}" > /tmp/key.gpg + - name: Set tagged nFPM release + if: matrix.goos == 'linux' && needs.channel.outputs.channel == 'release' + env: + REVISION: ${{ needs.channel.outputs.revision }} run: | - make windows_zip - rm -f release/checksums.sha256.txt - openssl dgst -r -sha256 release/* | sed 's#release/##' | tee release/checksums.sha256.txt - - name: upload artifacts - uses: actions/upload-artifact@v7 + set -euo pipefail + [[ ${REVISION} =~ ^[0-9]+$ ]] + awk -v r="${REVISION}" ' + { print } + /nfpm-release:/ { print " release: " r } + ' .goreleaser.yaml > .goreleaser.yaml.nfpm + mv .goreleaser.yaml.nfpm .goreleaser.yaml + - uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3 with: - name: release - path: release - - deploy-unstable-unstable: - # description: "Uploads pre-built binaries to unstable.golift.io." - if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/unstable' - strategy: - matrix: - files: [release, dmg-release] - needs: - - release-test - - macapp-test - name: Deploy Unstable.app - runs-on: ubuntu-latest - steps: - - name: "Download files: ${{ matrix.files }}" - uses: actions/download-artifact@v8 + distribution: goreleaser-pro + version: "~> v2" + args: release --clean --split --timeout 60m ${{ needs.channel.outputs.extra }} + env: + # GGOOS filters targets without leaking GOOS into before-hook `go run`. + GGOOS: ${{ matrix.goos }} + CHANNEL: ${{ needs.channel.outputs.channel }} + REVISION: ${{ needs.channel.outputs.revision }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GORELEASER_KEY: ${{ secrets.GORELEASER_PRO_KEY }} + USER: github-actions + GPG_SIGNING_KEY: /tmp/key.gpg + GPG_SIGNING_KEY_ID: ${{ steps.import_gpg.outputs.keyid }} + GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} + - name: Build FreeBSD pkgng txz + if: matrix.goos == 'freebsd' + env: + REVISION: ${{ needs.channel.outputs.revision }} + run: bash .github/scripts/freebsd_txz.sh dist/freebsd + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: ${{ matrix.files }} - - name: Upload files to unstable.golift.io - run: >- - set -euo pipefail; - for file in *.{zip,dmg,gz}; do - [ -f "$file" ] || continue; - echo "Uploading: ${file}"; - curl -sS --fail-with-body --retry 5 --retry-all-errors --retry-delay 2 - -H "X-API-KEY: ${{ secrets.UNSTABLE_UPLOAD_KEY }}" - "https://unstable.golift.io/upload.php?folder=unpackerr" -F "file=@${file}"; - versionfile="${{needs.release-test.outputs.version}};filename=${file}.txt;type=text/plain"; - curl -sS --fail-with-body --retry 5 --retry-all-errors --retry-delay 2 - -H "X-API-KEY: ${{ secrets.UNSTABLE_UPLOAD_KEY }}" - "https://unstable.golift.io/upload.php?folder=unpackerr" -F "file=${versionfile}"; - done + name: dist-${{ matrix.goos }} + path: dist/${{ matrix.goos }} + include-hidden-files: true + compression-level: 0 + if-no-files-found: error + retention-days: 1 - deploy-unstable-packagecloud: - # description: "Uploads pre-built RPM and DEB packages to packagecloud.io/golift" - if: github.ref == 'refs/heads/unstable' - needs: release-test - name: Deploy Unstable PackageCloud + # OIDC + mTLS certs stay off linux/freebsd. signerd is Windows-only. + split-windows: + name: "Build: windows" + needs: [channel, require-secrets] runs-on: ubuntu-latest + permissions: + contents: read + id-token: write steps: - - name: Download release files - uses: actions/download-artifact@v8 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - name: release - - uses: golift/upload-packagecloud@v1 + fetch-depth: 0 + ref: ${{ github.event_name == 'schedule' && 'refs/heads/main' || github.ref }} + - run: git fetch --force --tags + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: - userrepo: golift/unstable - apitoken: ${{ secrets.PACKAGECLOUD_TOKEN }} - packages: . - rpmdists: el/6 - debdists: ubuntu/focal - - deploy-packagecloud: - # description: "Uploads pre-built RPM and DEB packages to packagecloud.io/golift" - if: startsWith(github.ref, 'refs/tags/v') - needs: release-test - name: Deploy Release PackageCloud - runs-on: ubuntu-latest - steps: - - name: Download release files - uses: actions/download-artifact@v8 + go-version-file: go.mod + cache: true + - name: Create GPG_SIGNING_KEY file + env: + GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} + run: printf '%s\n' "${GPG_SIGNING_KEY}" > /tmp/key.gpg + - name: Install golift codesign + env: + GOBIN: ${{ runner.temp }}/codesign-bin + run: | + set -euo pipefail + mkdir -p "${GOBIN}" + go install golift.io/codesign/cmd/codesign@v1.0.3 + echo "${GOBIN}" >> "${GITHUB_PATH}" + echo "CODESIGN_BIN=${GOBIN}/codesign" >> "${GITHUB_ENV}" + - uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3 with: - name: release - - uses: golift/upload-packagecloud@v1 + distribution: goreleaser-pro + version: "~> v2" + args: release --clean --split --timeout 60m ${{ needs.channel.outputs.extra }} + env: + GGOOS: windows + CHANNEL: ${{ needs.channel.outputs.channel }} + REVISION: ${{ needs.channel.outputs.revision }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GORELEASER_KEY: ${{ secrets.GORELEASER_PRO_KEY }} + USER: github-actions + GPG_SIGNING_KEY: /tmp/key.gpg + CODESIGN_URL: ${{ secrets.CODESIGN_URL }} + CODESIGN_CLIENT_CERT: ${{ secrets.CODESIGN_CLIENT_CERT }} + CODESIGN_CLIENT_KEY: ${{ secrets.CODESIGN_CLIENT_KEY }} + CODESIGN_NAME: Unpackerr + CODESIGN_WEBSITE: https://unpackerr.zip + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - userrepo: golift/pkgs - apitoken: ${{ secrets.PACKAGECLOUD_TOKEN }} - packages: . - rpmdists: el/6 - debdists: ubuntu/focal + name: dist-windows + path: dist/windows + include-hidden-files: true + compression-level: 0 + if-no-files-found: error + retention-days: 1 - deploy-github-release: - # description: uploads all the built release assets to the GitHub Release. - if: startsWith(github.ref, 'refs/tags/v') + split-darwin: + name: "Build: darwin" + needs: [channel, require-secrets] + # Nightly is Docker-only; skip Apple notarization. + if: needs.channel.outputs.channel != 'nightly' + runs-on: macos-latest + timeout-minutes: 90 permissions: - contents: write - needs: [release-test, macapp-test] - strategy: - matrix: - files: [release, dmg-release] - name: Deploy GitHub Release - runs-on: ubuntu-latest + contents: read steps: - - name: Download ${{ matrix.files }} Files - uses: actions/download-artifact@v8 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - name: ${{ matrix.files }} - - name: Publish ${{ matrix.files }} artifacts to github - uses: softprops/action-gh-release@v3 + fetch-depth: 0 + ref: ${{ github.event_name == 'schedule' && 'refs/heads/main' || github.ref }} + - run: git fetch --force --tags + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: - files: | - *.rpm - *.deb - *.txz - *.zip - *.dmg - *.gz - *.txt - *.zst - *.sig - - archlinux-aur: - # description: creates and uploads and aur file to the golift/aur repo for arch linux. - needs: deploy-github-release - name: Deploy ArchLinux AUR - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 + go-version-file: go.mod + cache: true + - name: Import signing certificate and notary credentials + env: + MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }} + MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }} + MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }} + MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }} + MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} + run: bash .github/scripts/macos_keychain.sh + - uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3 with: - fetch-depth: '0' - - name: Deploy Arch AUR - run: bash init/archlinux/aur-deploy.sh + distribution: goreleaser-pro + version: "~> v2" + args: release --clean --split --timeout 60m ${{ needs.channel.outputs.extra }} env: - DEPLOY_KEY: ${{ secrets.AUR_DEPLOY_KEY }} + GGOOS: darwin + CHANNEL: ${{ needs.channel.outputs.channel }} + REVISION: ${{ needs.channel.outputs.revision }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GORELEASER_KEY: ${{ secrets.GORELEASER_PRO_KEY }} + USER: github-actions + KEYCHAIN_PATH: ${{ env.KEYCHAIN_PATH }} + MACOS_SIGN_IDENTITY: ${{ env.MACOS_SIGN_IDENTITY }} + MACOS_NOTARY_PROFILE_NAME: ${{ env.MACOS_NOTARY_PROFILE_NAME }} + - name: Staple notarized DMG + run: bash .github/scripts/macos_staple.sh dist/darwin + - name: Pack Darwin dist + run: | + set -euo pipefail + # hdiutil leaves a writable clone of Unpackerr.app next to the DMG. + # upload-artifact on macos hangs for many minutes on that file tree. + find dist/darwin/dmg -mindepth 2 -maxdepth 2 -type d -name 'Unpackerr' -exec rm -rf {} + + du -sh dist/darwin dist/darwin/* 2>/dev/null || true + tar -C dist/darwin -cf dist-darwin.tar . + ls -lh dist-darwin.tar + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: dist-darwin + path: dist-darwin.tar + compression-level: 0 + if-no-files-found: error + retention-days: 1 + env: + ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY: "5" - docker-ghcr-build-push: - if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/unstable' - name: Deploy GHCR Container + release: + name: "release ${{ needs.channel.outputs.revision }}" + needs: [channel, require-secrets, split, split-windows, split-darwin] + if: >- + always() && + !cancelled() && + needs.channel.result == 'success' && + needs.require-secrets.result == 'success' && + needs.split.result == 'success' && + needs.split-windows.result == 'success' && + (needs.split-darwin.result == 'success' || needs.split-darwin.result == 'skipped') runs-on: ubuntu-latest + timeout-minutes: 90 permissions: - contents: read + contents: write packages: write - # This is used to complete the identity challenge - # with sigstore/fulcio when running outside of PRs. - id-token: write - + env: + CHANNEL: ${{ needs.channel.outputs.channel }} steps: - - name: Checkout repository - uses: actions/checkout@v7 - + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + ref: ${{ github.event_name == 'schedule' && 'refs/heads/main' || github.ref }} + - run: git fetch --force --tags + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version-file: go.mod + cache: true + - name: Import GPG key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0 + with: + gpg_private_key: ${{ secrets.GPG_SIGNING_KEY }} - name: Set up QEMU - uses: docker/setup-qemu-action@v4 + uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 with: - platforms: 'arm64' - - # Workaround: https://github.com/docker/build-push-action/issues/461 - - name: Setup Docker buildx - uses: docker/setup-buildx-action@v4 - - # Login against a Docker registry except on PR - # https://github.com/docker/login-action - - name: Log into registry ghcr.io - if: github.event_name != 'pull_request' - uses: docker/login-action@v4 + platforms: arm64,arm + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 + - name: Log into GHCR + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - # Extract metadata (tags, labels) for Docker - # https://github.com/docker/metadata-action - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@v6 + - name: Log into Docker Hub + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: - images: ghcr.io/${{ github.repository }} - tags: | - type=semver,pattern=v{{version}} - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - type=ref,enable=true,event=branch - - - name: Set docker build-args - run: >- - git fetch --force --tags; - source ./settings.sh; - for item in DATE COMMIT VERSION ITERATION BRANCH LICENSE DESC VENDOR MAINT SOURCE_URL; do - eval echo "settings.sh build-arg: ${item}=\$${item}"; - eval echo "${item}=\$${item}" >> $GITHUB_ENV; + username: golift + password: ${{ secrets.DOCKERHUB_PASSWORD }} + - name: Enable Docker Hub publish + run: echo DOCKERHUB_PUBLISH=1 >> "$GITHUB_ENV" + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: dist-linux + path: dist/linux + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: dist-windows + path: dist/windows + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: dist-freebsd + path: dist/freebsd + - name: Verify FreeBSD txz packages + run: | + set -euo pipefail + ls -l dist/freebsd/unpackerr-*.txz + for arch in amd64 i386 armhf arm64; do + ls dist/freebsd/unpackerr-*."${arch}".txz done - - # Build and push Docker image with Buildx (don't push on PR) - # https://github.com/docker/build-push-action - - name: Build and push Docker image - id: build-and-push - uses: docker/build-push-action@v7 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + if: needs.split-darwin.result == 'success' + with: + name: dist-darwin + path: dist + - name: Unpack Darwin dist + if: needs.split-darwin.result == 'success' + run: | + set -euo pipefail + mkdir -p dist/darwin + tar -xf dist/dist-darwin.tar -C dist/darwin + rm -f dist/dist-darwin.tar + - uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3 + with: + distribution: goreleaser-pro + version: "~> v2" + args: continue --merge --timeout 60m + env: + CHANNEL: ${{ needs.channel.outputs.channel }} + REVISION: ${{ needs.channel.outputs.revision }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GORELEASER_KEY: ${{ secrets.GORELEASER_PRO_KEY }} + HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} + USER: github-actions + PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }} + AUR_DEPLOY_KEY: ${{ secrets.AUR_DEPLOY_KEY }} + GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} + - name: Flatten Linux packages for packagecloud + if: env.CHANNEL != 'nightly' + run: | + set -euo pipefail + find dist -type f \( -name '*.deb' -o -name '*.rpm' \) -print0 | + while IFS= read -r -d '' f; do + base="$(basename "$f")" + if [ ! -e "dist/${base}" ]; then + cp "$f" "dist/${base}" + fi + done + - uses: golift/upload-packagecloud@eb0067369b59340b04dce218010960ebe74720b2 # v1.1.1 + if: env.CHANNEL != 'nightly' with: - platforms: |- - linux/amd64 - linux/arm64 - context: . - file: init/docker/Dockerfile - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - cache-from: type=gha - cache-to: type=gha,mode=max - build-args: | - BUILD_DATE=${{ env.DATE }} - COMMIT=${{ env.COMMIT }} - VERSION=${{ env.VERSION }} - ITERATION=${{ env.ITERATION }} - BRANCH=${{ env.BRANCH }} - LICENSE=${{ env.LICENSE }} - DESC=${{ env.DESC }} - VENDOR=${{ env.VENDOR }} - AUTHOR=${{ env.MAINT }} - SOURCE_URL=${{ env.SOURCE_URL }} + userrepo: ${{ env.CHANNEL == 'release' && 'golift/pkgs' || 'golift/unstable' }} + apitoken: ${{ secrets.PACKAGECLOUD_TOKEN }} + packages: dist/ + rpmdists: el/6 + debdists: ubuntu/focal + - name: Upload to unstable.golift.io + if: env.CHANNEL == 'unstable' + env: + UNSTABLE_UPLOAD_KEY: ${{ secrets.UNSTABLE_UPLOAD_KEY }} + CHANNEL: unstable + run: bash .github/scripts/unstable_upload.sh dist diff --git a/.gitignore b/.gitignore index 5eb8438c..69e086db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /unpackerr +/dist/ +__pycache__/ # JetBrains IDEs: GoLand, IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm .idea/* diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 00000000..7e2316ca --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,575 @@ +# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json +version: 2 +pro: true + +project_name: unpackerr + +# GoReleaser --nightly is "not a tagged GitHub release". CHANNEL picks the Docker tag: +# unstable = push to the unstable branch (manual) +# nightly = daily cron from main +# release = v* tags +nightly: + # This is why REVISION is still computed (once, in the channel job) even + # though {{ .Version }} already includes it on --nightly: this template is + # what *creates* that version. Tagged builds keep .Version as the semver + # tag; ldflags Revision and Darwin CFBundleVersion still need the count. + version_template: "{{ incpatch .Version }}-{{ .Env.REVISION }}" + +before: + hooks: + - go mod tidy + - go generate ./... + - go run github.com/davidnewhall/md2roff@v0.0.1 --manual unpackerr --version "{{.Version}}" --date "{{.Date}}" examples/MANUAL.md + - go run github.com/davidnewhall/md2roff@v0.0.1 --manual unpackerr --version "{{.Version}}" --date "{{.Date}}" README.md + - gzip -9nf examples/MANUAL + - mv -f examples/MANUAL.gz unpackerr.1.gz + - go run github.com/akavel/rsrc@v0.10.2 -arch amd64 -ico init/windows/application.ico -manifest init/windows/manifest.xml -o rsrc_windows_amd64.syso + +builds: + - id: unpackerr + env: + - CGO_ENABLED=0 + goos: + - linux + goarch: + - amd64 + - arm + - arm64 + - '386' + # One linux ARM (v7). nFPM maps GOARM 6 and 7 both to Debian armhf, so + # packaging both collides on packagecloud. Docker is linux/arm/v7. + goarm: + - '7' + flags: + - -trimpath + tags: + - osusergo + - netgo + ldflags: + - -s -w + - -X "golift.io/version.Version={{.Version}}" + - -X "golift.io/version.BuildDate={{.Date}}" + - -X "golift.io/version.BuildUser={{.Env.USER}}" + - -X "golift.io/version.Revision={{.Env.REVISION}}" + - -X "golift.io/version.Branch={{.ShortCommit}} [{{.Branch}}]" + # energye/systray's Darwin backend is Objective-C (Cocoa). CGO_ENABLED=0 + # drops those symbols (undefined: nativeLoop, setInternalLoop, ...). + # CI builds this on macos-latest (host clang). OSX_CROSS is only for a + # local Linux snapshot with osxcross (o64-clang / oa64-clang). + - id: unpackerr-darwin + env: + - CGO_ENABLED=1 + # Go 1.27's darwin objects are macOS 13; a lower min just makes ld warn. + - CGO_LDFLAGS=-mmacosx-version-min=13.0 + - CGO_CFLAGS=-mmacosx-version-min=13.0 + - MACOSX_DEPLOYMENT_TARGET=13.0 + - 'CC={{ if isEnvSet "OSX_CROSS" }}{{ if eq .Arch "amd64" }}o64-clang{{ else }}oa64-clang{{ end }}{{ else }}clang{{ end }}' + - 'CXX={{ if isEnvSet "OSX_CROSS" }}{{ if eq .Arch "amd64" }}o64-clang++{{ else }}oa64-clang++{{ end }}{{ else }}clang++{{ end }}' + goos: + - darwin + goarch: + - amd64 + - arm64 + flags: + - -trimpath + tags: + - osusergo + - netgo + ldflags: + - -s -w + - -X "golift.io/version.Version={{.Version}}" + - -X "golift.io/version.BuildDate={{.Date}}" + - -X "golift.io/version.BuildUser={{.Env.USER}}" + - -X "golift.io/version.Revision={{.Env.REVISION}}" + - -X "golift.io/version.Branch={{.ShortCommit}} [{{.Branch}}]" + - id: unpackerr-freebsd + env: + - CGO_ENABLED=0 + goos: + - freebsd + goarch: + - amd64 + - arm + - arm64 + - '386' + # One ARM (v7). Dual GOARM would collide on unpackerr-*.armhf.txz. + goarm: + - '7' + flags: + - -trimpath + tags: + - osusergo + - netgo + ldflags: + - -s -w + - -X "golift.io/version.Version={{.Version}}" + - -X "golift.io/version.BuildDate={{.Date}}" + - -X "golift.io/version.BuildUser={{.Env.USER}}" + - -X "golift.io/version.Revision={{.Env.REVISION}}" + - -X "golift.io/version.Branch={{.ShortCommit}} [{{.Branch}}]" + - id: unpackerr-windows + env: + - CGO_ENABLED=0 + goos: + - windows + goarch: + - amd64 + flags: + - -trimpath + tags: + - osusergo + - netgo + ldflags: + - -s -w + - -H=windowsgui + - -X "golift.io/version.Version={{.Version}}" + - -X "golift.io/version.BuildDate={{.Date}}" + - -X "golift.io/version.BuildUser={{.Env.USER}}" + - -X "golift.io/version.Revision={{.Env.REVISION}}" + - -X "golift.io/version.Branch={{.ShortCommit}} [{{.Branch}}]" + hooks: + post: + - cmd: bash init/windows/signexe.sh "{{ .Path }}" + +universal_binaries: + - id: unpackerr-darwin + ids: [unpackerr-darwin] + replace: true + name_template: unpackerr + +# Quill (Linux) can only sign/notarize the raw binary. A Gatekeeper-valid +# .app inside a DMG has to be codesign'd + notarytool'd on macOS. +# Enabled when the Darwin split job imported the P12 into a keychain. +notarize: + macos_native: + - enabled: '{{ isEnvSet "KEYCHAIN_PATH" }}' + # These pipes match Extra.ID on the .app / .dmg, not the Darwin build id. + # unpackerr-darwin made both skip: "no app bundles found" / "no artifacts found". + ids: + - unpackerr-app + - unpackerr-dmg + use: dmg + sign: + keychain: "{{ .Env.KEYCHAIN_PATH }}" + identity: "{{ .Env.MACOS_SIGN_IDENTITY }}" + options: [runtime] + entitlements: init/macos/entitlements.plist + notarize: + profile_name: "{{ .Env.MACOS_NOTARY_PROFILE_NAME }}" + wait: true + +app_bundles: + - id: unpackerr-app + name: Unpackerr + ids: [unpackerr-darwin] + icon: init/macos/Unpackerr.app/Contents/Resources/Unpackerr.icns + bundle: io.golift.unpackerr + extra_files: + - src: init/macos/Unpackerr.app/Contents/Resources/terminal-notifier.app + dst: Contents/Resources/terminal-notifier.app + - src: examples/unpackerr.conf.example + dst: Contents/Resources/unpackerr.conf.example + - src: unpackerr.1.gz + dst: Contents/Resources/unpackerr.1.gz + templated_extra_files: + - src: init/macos/Info.plist.tmpl + dst: Contents/Info.plist + +dmg: + - id: unpackerr-dmg + name: Unpackerr + ids: [unpackerr-app] + use: appbundle + +source: + enabled: true + name_template: '{{ .ProjectName }}-{{ .Version }}' + prefix_template: '{{ .ProjectName }}-{{ .Version }}/' + +archives: + - id: default + ids: [unpackerr, unpackerr-darwin] + formats: [tar.gz] + # Flat tarball for GitHub. Homebrew installs the notarized DMG, not this archive. + wrap_in_directory: false + files: + - LICENSE + - README.md + - README.html + - src: examples/unpackerr.conf.example + dst: unpackerr.conf.example + - src: unpackerr.1.gz + dst: unpackerr.1.gz + - src: examples/MANUAL.html + dst: unpackerr-manual.html + info: + mode: 0644 + - src: examples/docker-compose.yml + dst: docker-compose.yml + # pkg/update/check.go only matches assets ending in .exe.zip. + - id: windows + ids: [unpackerr-windows] + formats: [zip] + name_template: '{{ .ProjectName }}.{{ .Arch }}.exe' + wrap_in_directory: false + files: + - LICENSE + - src: examples/unpackerr.conf.example + dst: unpackerr.conf.example + - src: examples/MANUAL.html + dst: unpackerr-manual.html + - README.html + # Real pkgng .txz is built after --split by fpm (-t freebsd), same as the + # old Makefile. nFPM has no freebsd target; a tar.xz is not `pkg install`-able. + +nfpms: + - id: unpackerr-packages + ids: [unpackerr] + # Default is unpackerr_VERSION_linux_386.deb. Conventional names match + # v0.15.2 (unpackerr_0.15.2-960_i386.deb). nFPM uses ~ for semver + # prerelease; GitHub replaces tilde with a dot, so swap it for a hyphen. + file_name_template: '{{ replace "~" "-" .ConventionalFileName }}' + vendor: Go Lift + homepage: https://unpackerr.zip + maintainer: David Newhall II + description: Extracts downloads so Radarr, Sonarr, Lidarr or Readarr may import them. + license: MIT + # GoReleaser does not template nfpms.release. Tagged linux split inserts + # `release: REVISION` after this marker. --nightly leaves it unset. + # nfpm-release: + formats: + - deb + - rpm + - archlinux + bindir: /usr/bin + section: default + priority: extra + provides: + - unpackerr + rpm: + signature: + key_file: "{{ .Env.GPG_SIGNING_KEY }}" + deb: + signature: + key_file: "{{ .Env.GPG_SIGNING_KEY }}" + type: origin + scripts: + preinstall: init/systemd/before-install.sh + postinstall: init/systemd/after-install.sh + preremove: init/systemd/before-remove.sh + contents: + - dst: /etc/unpackerr + type: dir + file_info: + mode: 0755 + - src: examples/unpackerr.conf.example + dst: /etc/unpackerr/unpackerr.conf + type: config|noreplace + file_info: + mode: 0644 + - src: examples/unpackerr.conf.example + dst: /etc/unpackerr/unpackerr.conf.example + type: config + file_info: + mode: 0644 + - src: unpackerr.1.gz + dst: /usr/share/man/man1/unpackerr.1.gz + file_info: + mode: 0644 + - src: LICENSE + dst: /usr/share/doc/unpackerr/LICENSE + file_info: + mode: 0644 + - src: examples/MANUAL.html + dst: /usr/share/doc/unpackerr/unpackerr-manual.html + file_info: + mode: 0644 + - src: README.html + dst: /usr/share/doc/unpackerr/README.html + file_info: + mode: 0644 + - src: examples/docker-compose.yml + dst: /usr/share/doc/unpackerr/docker-compose.yml + file_info: + mode: 0644 + - src: examples/unpackerr.conf.example + dst: /usr/share/doc/unpackerr/unpackerr.conf.example + file_info: + mode: 0644 + - src: init/systemd/unpackerr.service + dst: /usr/lib/systemd/system/unpackerr.service + type: config + file_info: + mode: 0644 + - src: init/systemd/unpackerr.tmpfiles + dst: /usr/lib/tmpfiles.d/unpackerr.conf + file_info: + mode: 0644 + +dockers_v2: + - id: unpackerr-release + disable: '{{ ne .Env.CHANNEL "release" }}' + dockerfile: init/docker/Dockerfile + ids: [unpackerr] + images: + - ghcr.io/unpackerr/unpackerr + tags: + - latest + - "{{ .Version }}" + - "v{{ .Version }}" + - "{{ .Major }}.{{ .Minor }}" + - "{{ .Major }}" + platforms: &docker_platforms + - linux/amd64 + - linux/arm64 + - linux/arm/v7 + labels: &docker_labels + org.opencontainers.image.created: "{{ .Date }}" + org.opencontainers.image.title: Unpackerr + org.opencontainers.image.revision: "{{ .FullCommit }}" + org.opencontainers.image.version: "{{ .Version }}" + org.opencontainers.image.documentation: https://unpackerr.zip/docs/install/docker + org.opencontainers.image.description: Extracts downloads so Radarr, Sonarr, Lidarr or Readarr may import them. + org.opencontainers.image.url: https://unpackerr.zip + org.opencontainers.image.source: "{{ .GitURL }}" + org.opencontainers.image.vendor: Go Lift + org.opencontainers.image.licenses: MIT + - id: unpackerr-release-hub + disable: '{{ or (ne .Env.CHANNEL "release") (not (isEnvSet "DOCKERHUB_PUBLISH")) }}' + dockerfile: init/docker/Dockerfile + ids: [unpackerr] + images: + - docker.io/golift/unpackerr + tags: + - latest + - "{{ .Version }}" + - "v{{ .Version }}" + - "{{ .Major }}.{{ .Minor }}" + - "{{ .Major }}" + platforms: *docker_platforms + labels: *docker_labels + - id: unpackerr-unstable + disable: '{{ ne .Env.CHANNEL "unstable" }}' + dockerfile: init/docker/Dockerfile + ids: [unpackerr] + images: + - ghcr.io/unpackerr/unpackerr + tags: + - unstable + platforms: *docker_platforms + labels: *docker_labels + - id: unpackerr-unstable-hub + disable: '{{ or (ne .Env.CHANNEL "unstable") (not (isEnvSet "DOCKERHUB_PUBLISH")) }}' + dockerfile: init/docker/Dockerfile + ids: [unpackerr] + images: + - docker.io/golift/unpackerr + tags: + - unstable + platforms: *docker_platforms + labels: *docker_labels + - id: unpackerr-nightly + disable: '{{ ne .Env.CHANNEL "nightly" }}' + dockerfile: init/docker/Dockerfile + ids: [unpackerr] + images: + - ghcr.io/unpackerr/unpackerr + tags: + - nightly + platforms: *docker_platforms + labels: *docker_labels + - id: unpackerr-nightly-hub + disable: '{{ or (ne .Env.CHANNEL "nightly") (not (isEnvSet "DOCKERHUB_PUBLISH")) }}' + dockerfile: init/docker/Dockerfile + ids: [unpackerr] + images: + - docker.io/golift/unpackerr + tags: + - nightly + platforms: *docker_platforms + labels: *docker_labels + +aur_sources: + - name: unpackerr + disable: "{{ .IsNightly }}" + homepage: https://unpackerr.zip + description: Extracts downloads so Radarr, Sonarr, Lidarr or Readarr may import them. + license: MIT + git_url: ssh://aur@aur.archlinux.org/unpackerr.git + private_key: "{{ .Env.AUR_DEPLOY_KEY }}" + commit_author: + name: goreleaserbot + email: bot@goreleaser.com + commit_msg_template: "Update unpackerr to {{ .Tag }}" + maintainers: + - David Newhall II + - Donald Webster + provides: + - unpackerr + makedepends: + - go + - gzip + optdepends: + - 'transmission-cli: torrent downloader (CLI and daemon)' + - 'transmission-gtk: torrent downloader (GTK+)' + - 'transmission-qt: torrent downloader (Qt)' + - 'deluge: torrent downloader' + - 'rtorrent: torrent downloader' + backup: + - etc/unpackerr/unpackerr.conf + arches: + - x86_64 + - aarch64 + - arm + - armv6h + - armv7h + - i686 + - pentium4 + prepare: | + cd "${pkgname}-${pkgver}" + mkdir -p build + build: | + cd "${pkgname}-${pkgver}" + export GOFLAGS="-buildmode=pie -trimpath -modcacherw" + LDFLAGS="-w -s -X golift.io/version.Version=${pkgver} \ + -X golift.io/version.Revision=${pkgrel} \ + -X golift.io/version.BuildDate=$(date -u +%Y-%m-%dT%H:%M:00Z) \ + -X golift.io/version.BuildUser=$(whoami || echo unknown) \ + -X \"golift.io/version.Branch=${pkgver} [aur]\"" + go build -o unpackerr -ldflags "${LDFLAGS}" . + go run github.com/davidnewhall/md2roff@v0.0.1 --manual unpackerr --version "${pkgver}" --date "$(date -u +%Y-%m-%d)" examples/MANUAL.md + go run github.com/davidnewhall/md2roff@v0.0.1 --manual unpackerr --version "${pkgver}" --date "$(date -u +%Y-%m-%d)" README.md + gzip -9nf examples/MANUAL + mv examples/MANUAL.gz unpackerr.1.gz + package: | + cd "${pkgname}-${pkgver}" + install -d -m 755 "${pkgdir}/usr/share/licenses/${pkgname}" "${pkgdir}/usr/share/doc/${pkgname}" "${pkgdir}/etc/${pkgname}" + install -D -m 755 unpackerr "${pkgdir}/usr/bin/unpackerr" + install -D -m 644 examples/unpackerr.conf.example "${pkgdir}/etc/unpackerr/unpackerr.conf" + install -D -m 644 examples/unpackerr.conf.example "${pkgdir}/etc/unpackerr/unpackerr.conf.example" + install -D -m 644 LICENSE "${pkgdir}/usr/share/licenses/unpackerr/LICENSE" + install -D -m 644 examples/MANUAL.html "${pkgdir}/usr/share/doc/${pkgname}/unpackerr-manual.html" + install -D -m 644 README.html "${pkgdir}/usr/share/doc/${pkgname}/README.html" + install -D -m 644 examples/docker-compose.yml "${pkgdir}/usr/share/doc/${pkgname}/docker-compose.yml" + install -D -m 644 examples/unpackerr.conf.example "${pkgdir}/usr/share/doc/${pkgname}/unpackerr.conf.example" + install -D -m 644 unpackerr.1.gz "${pkgdir}/usr/share/man/man1/unpackerr.1.gz" + install -D -m 644 init/systemd/unpackerr.service "${pkgdir}/usr/lib/systemd/system/unpackerr.service" + echo 'u unpackerr - "unpackerr daemon"' > unpackerr.sysusers + install -D -m 644 unpackerr.sysusers "${pkgdir}/usr/lib/sysusers.d/unpackerr.conf" + install -D -m 644 init/systemd/unpackerr.tmpfiles "${pkgdir}/usr/lib/tmpfiles.d/unpackerr.conf" + +homebrew_casks: + - name: unpackerr + # Notarized DMG (macos_native signs unpackerr-app / unpackerr-dmg). The + # unsigned darwin tar.gz (ids: default) is GitHub-only; Gatekeeper rejects it. + ids: [unpackerr-dmg] + app: Unpackerr.app + binaries: + - '#{appdir}/Unpackerr.app/Contents/MacOS/unpackerr' + skip_upload: "{{ .IsNightly }}" + repository: + owner: golift + name: homebrew-mugs + branch: master + token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" + commit_author: + name: goreleaserbot + email: bot@goreleaser.com + commit_msg_template: "Brew cask update for {{ .ProjectName }} version {{ .Tag }}" + directory: Casks + homepage: https://unpackerr.zip + description: Extracts downloads so Radarr, Sonarr, Lidarr or Readarr may import them. + license: MIT + url: + template: "https://github.com/Unpackerr/unpackerr/releases/download/{{ .Tag }}/{{ .ArtifactName }}" + verified: github.com/Unpackerr/unpackerr + # Do not set `service:` — cask `service` is ~/Library/Services (Automator), not LaunchAgents. + # Install a LaunchAgent in postflight; brew services is formula-only. + hooks: + post: + install: | + staged_app = "#{staged_path}/Unpackerr.app" + conf_src = "#{staged_app}/Contents/Resources/unpackerr.conf.example" + conf_dir = HOMEBREW_PREFIX/"etc/unpackerr" + conf_dir.mkpath + FileUtils.cp conf_src, conf_dir/"unpackerr.conf.example" + had_conf = (conf_dir/"unpackerr.conf").exist? + FileUtils.cp conf_src, conf_dir/"unpackerr.conf" unless had_conf + man1 = HOMEBREW_PREFIX/"share/man/man1" + man1.mkpath + FileUtils.cp "#{staged_app}/Contents/Resources/unpackerr.1.gz", man1/"unpackerr.1.gz" + (HOMEBREW_PREFIX/"var/log").mkpath + label = "io.golift.unpackerr" + agent = Pathname.new(Dir.home)/"Library/LaunchAgents/#{label}.plist" + agent.dirname.mkpath + binary = "#{appdir}/Unpackerr.app/Contents/MacOS/unpackerr" + conf = conf_dir/"unpackerr.conf" + log = HOMEBREW_PREFIX/"var/log/unpackerr.log" + agent.write <<~XML + + + + + Label#{label} + ProgramArguments + + #{binary} + --config + #{conf} + + RunAtLoad + KeepAlive + WorkingDirectory#{HOMEBREW_PREFIX} + StandardOutPath#{log} + StandardErrorPath#{log} + + + XML + if had_conf + uid = Process.uid + system "/bin/launchctl", "bootout", "gui/#{uid}/#{label}" + system "/bin/launchctl", "bootstrap", "gui/#{uid}", agent.to_s + end + uninstall: | + label = "io.golift.unpackerr" + agent = Pathname.new(Dir.home)/"Library/LaunchAgents/#{label}.plist" + system "/bin/launchctl", "bootout", "gui/#{Process.uid}/#{label}" + agent.delete if agent.exist? + man = HOMEBREW_PREFIX/"share/man/man1/unpackerr.1.gz" + man.delete if man.exist? + caveats: | + First install: edit #{HOMEBREW_PREFIX}/etc/unpackerr/unpackerr.conf then: + launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/io.golift.unpackerr.plist + Upgrades restart the LaunchAgent when that config already exists. + Restart: launchctl kickstart -k gui/$(id -u)/io.golift.unpackerr + Stop: launchctl bootout gui/$(id -u)/io.golift.unpackerr + (brew services does not manage casks.) + Menu bar: open Unpackerr.app (do not also run the LaunchAgent). + The LaunchAgent runs the signed CLI inside the app (no menu bar). + The manual explains the config file options: man unpackerr + +checksum: + name_template: checksums.sha256.txt + algorithm: sha256 + +signs: + - artifacts: checksum + # Detached .pkg.tar.zst.sig like the old Makefile. Deb/rpm already have + # embedded signatures; pacman wants a sidecar. + - id: archlinux + artifacts: package + # Merge artifact filters do not load sprig (hasSuffix is undefined there). + # eq is a Go template builtin; Extra.Format is archlinux/deb/rpm. + if: '{{ eq .Format "archlinux" }}' + +release: + disable: "{{ .IsNightly }}" + name_template: "v{{ .Version }}" + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" + - "^ci:" diff --git a/Makefile b/Makefile deleted file mode 100644 index d71a5d98..00000000 --- a/Makefile +++ /dev/null @@ -1,422 +0,0 @@ -# This Makefile is written as generic as possible. -# Setting the variables in settings.sh and creating the paths in the repo makes this work. -# See more: https://github.com/golift/application-builder - -# Suck in our application information. -IGNORED:=$(shell bash -c "source settings.sh ; env | grep -v BASH_FUNC | sed 's/=/:=/;s/^/export /' > /tmp/.metadata.make") - -BUILD_FLAGS=-tags osusergo,netgo -GOFLAGS=-trimpath -mod=readonly -modcacherw - -# Preserve the passed-in version & iteration (homebrew). -_VERSION:=$(VERSION) -_ITERATION:=$(ITERATION) -include /tmp/.metadata.make - -# Travis CI passes the version in. Local builds get it from the current git tag. -ifneq ($(_VERSION),) -VERSION:=$(_VERSION) -ITERATION:=$(_ITERATION) -endif - -# rpm is wierd and changes - to _ in versions. -RPMVERSION:=$(shell echo $(VERSION) | tr -- - _) - -define PACKAGE_ARGS ---before-install init/systemd/before-install.sh \ ---after-install init/systemd/after-install.sh \ ---before-remove init/systemd/before-remove.sh \ ---name unpackerr \ ---deb-no-default-config-files \ ---rpm-os linux \ ---iteration $(ITERATION) \ ---license $(LICENSE) \ ---url $(SOURCE_URL) \ ---maintainer "$(MAINT)" \ ---vendor "$(VENDOR)" \ ---description "$(DESC)" \ ---config-files "/etc/unpackerr/unpackerr.conf" \ ---freebsd-origin "$(SOURCE_URL)" -endef - - -VERSION_LDFLAGS:= -X \"golift.io/version.Branch=$(BRANCH) ($(COMMIT))\" \ - -X \"golift.io/version.BuildDate=$(DATE)\" \ - -X \"golift.io/version.BuildUser=$(shell whoami)\" \ - -X \"golift.io/version.Revision=$(ITERATION)\" \ - -X \"golift.io/version.Version=$(VERSION)\" - -WINDOWS_LDFLAGS:= -H=windowsgui - -# CI sets WINDOWS_ZIP=0 so golift/codesign@v1 can Authenticode-sign the exe -# before this zip loop deletes it. Local `make release` still zips. -WINDOWS_ZIP ?= 1 - -# Makefile targets follow. - -all: clean build - -#################### -##### Releases ##### -#################### - -# Prepare a release. -release: clean linux_packages freebsd_packages windows - # Preparing a release! - mkdir -p $@ - mv unpackerr.*.linux unpackerr.*.freebsd $@/ - gzip -9r $@/ -ifneq ($(WINDOWS_ZIP),0) - for i in unpackerr*.exe ; do zip -9qj $@/$$i.zip $$i examples/*.example *.html; rm -f $$i;done -endif - mv *.rpm *.deb *.txz *.zst *.sig $@/ - # Generating File Hashes - openssl dgst -r -sha256 $@/* | sed 's#release/##' | tee $@/checksums.sha256.txt - -# requires a mac. -signdmg: Unpackerr.app - bash init/macos/makedmg.sh - -# Delete all build assets. -clean: - rm -f unpackerr unpackerr.*.{macos,freebsd,linux,exe}{,.gz,.zip} unpackerr.1{,.gz} unpackerr.rb - rm -f unpackerr{_,-}*.{deb,rpm,txz,zst,sig} v*.tar.gz.sha256 examples/MANUAL .metadata.make rsrc_*.syso - rm -f cmd/unpackerr/README{,.html} README{,.html} ./unpackerr_manual.html rsrc.syso Unpackerr.*.app.zip - rm -f PKGBUILD - rm -rf package_build_* release Unpackerr.*.app Unpackerr.app - -#################### -##### Sidecars ##### -#################### - -# Build a man page from a markdown file using md2roff. -# This also turns the repo readme into an html file. -# md2roff is needed to build the man file and html pages from the READMEs. -man: unpackerr.1.gz -unpackerr.1.gz: - # Building man page. Build dependency first: md2roff - go run github.com/davidnewhall/md2roff@v0.0.1 --manual unpackerr --version $(VERSION) --date "$(DATE)" examples/MANUAL.md - gzip -9nc examples/MANUAL > $@ - mv examples/MANUAL.html unpackerr_manual.html - -# TODO: provide a template that adds the date to the built html file. -readme: README.html -README.html: - # This turns README.md into README.html - go run github.com/davidnewhall/md2roff@v0.0.1 --manual unpackerr --version $(VERSION) --date "$(DATE)" README.md - -rsrc: rsrc.syso -rsrc.syso: init/windows/application.ico init/windows/manifest.xml - go run github.com/akavel/rsrc@latest -arch amd64 -ico init/windows/application.ico -manifest init/windows/manifest.xml - -generate: examples/unpackerr.conf.example -examples/unpackerr.conf.example: init/config/* - find pkg -name .DS\* -delete - go generate ./... - -#################### -##### Binaries ##### -#################### - -build: unpackerr -unpackerr: generate main.go - go build $(BUILD_FLAGS) -o unpackerr -ldflags "-w -s $(VERSION_LDFLAGS) $(EXTRA_LDFLAGS) " - -linux: unpackerr.amd64.linux -unpackerr.amd64.linux: generate main.go - # Building linux 64-bit x86 binary. - GOOS=linux GOARCH=amd64 go build $(BUILD_FLAGS) -o $@ -ldflags "-w -s $(VERSION_LDFLAGS) $(EXTRA_LDFLAGS) " - -linux386: unpackerr.386.linux -unpackerr.386.linux: generate main.go - # Building linux 32-bit x86 binary. - GOOS=linux GOARCH=386 go build $(BUILD_FLAGS) -o $@ -ldflags "-w -s $(VERSION_LDFLAGS) $(EXTRA_LDFLAGS) " - -arm: arm64 armhf - -arm64: unpackerr.arm64.linux -unpackerr.arm64.linux: generate main.go - # Building linux 64-bit ARM binary. - GOOS=linux GOARCH=arm64 go build $(BUILD_FLAGS) -o $@ -ldflags "-w -s $(VERSION_LDFLAGS) $(EXTRA_LDFLAGS) " - -armhf: unpackerr.arm.linux -unpackerr.arm.linux: generate main.go - # Building linux 32-bit ARM binary. - GOOS=linux GOARCH=arm GOARM=6 go build $(BUILD_FLAGS) -o $@ -ldflags "-w -s $(VERSION_LDFLAGS) $(EXTRA_LDFLAGS) " - -macos: unpackerr.universal.macos -unpackerr.universal.macos: unpackerr.amd64.macos unpackerr.arm64.macos - # Building darwin 64-bit universal binary. - lipo -create -output $@ unpackerr.amd64.macos unpackerr.arm64.macos -unpackerr.amd64.macos: generate main.go - # Building darwin 64-bit x86 binary. - GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 CGO_LDFLAGS=-mmacosx-version-min=10.8 CGO_CFLAGS=-mmacosx-version-min=10.8 go build $(BUILD_FLAGS) -o $@ -ldflags "-v -w -s $(VERSION_LDFLAGS) $(EXTRA_LDFLAGS) " -unpackerr.arm64.macos: generate main.go - # Building darwin 64-bit arm binary. - GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 CGO_LDFLAGS=-mmacosx-version-min=10.8 CGO_CFLAGS=-mmacosx-version-min=10.8 go build $(BUILD_FLAGS) -o $@ -ldflags "-v -w -s $(VERSION_LDFLAGS) $(EXTRA_LDFLAGS) " - - -freebsd: unpackerr.amd64.freebsd -unpackerr.amd64.freebsd: generate main.go - GOOS=freebsd GOARCH=amd64 go build $(BUILD_FLAGS) -o $@ -ldflags "-w -s $(VERSION_LDFLAGS) $(EXTRA_LDFLAGS) " - -freebsd386: unpackerr.i386.freebsd -unpackerr.i386.freebsd: generate main.go - GOOS=freebsd GOARCH=386 go build $(BUILD_FLAGS) -o $@ -ldflags "-w -s $(VERSION_LDFLAGS) $(EXTRA_LDFLAGS) " - -freebsdarm: unpackerr.armhf.freebsd -unpackerr.armhf.freebsd: generate main.go - GOOS=freebsd GOARCH=arm go build $(BUILD_FLAGS) -o $@ -ldflags "-w -s $(VERSION_LDFLAGS) $(EXTRA_LDFLAGS) " - -exe: unpackerr.amd64.exe -windows: unpackerr.amd64.exe -unpackerr.amd64.exe: generate rsrc.syso main.go - # Building windows 64-bit x86 binary. - GOOS=windows GOARCH=amd64 go build $(BUILD_FLAGS) -o $@ -ldflags "-w -s $(VERSION_LDFLAGS) $(EXTRA_LDFLAGS) $(WINDOWS_LDFLAGS)" - bash init/windows/signexe.sh $@ - -# Zip Windows exe after Authenticode (used by CI when WINDOWS_ZIP=0). -windows_zip: - mkdir -p release - for i in unpackerr*.exe ; do \ - [ -f $$i ] || continue; \ - zip -9qj release/$$i.zip $$i examples/*.example *.html; \ - rm -f $$i; \ - done - -#################### -##### Packages ##### -#################### - -linux_packages: rpm deb zst rpm386 deb386 debarm rpmarm zstarm debarmhf rpmarmhf zstarmhf - -freebsd_packages: freebsd_pkg freebsd386_pkg freebsdarm_pkg - -macapp: Unpackerr.app -Unpackerr.app: unpackerr.universal.macos - cp -rp init/macos/Unpackerr.app Unpackerr.app - mkdir -p Unpackerr.app/Contents/MacOS - cp unpackerr.universal.macos Unpackerr.app/Contents/MacOS/Unpackerr - sed -i '' -e "s/{{VERSION}}/$(VERSION)/g" Unpackerr.app/Contents/Info.plist - -rpm: unpackerr-$(RPMVERSION)-$(ITERATION).x86_64.rpm -unpackerr-$(RPMVERSION)-$(ITERATION).x86_64.rpm: package_build_linux_rpm check_fpm - @echo "Building 'rpm' package for unpackerr version '$(RPMVERSION)-$(ITERATION)'." - fpm -s dir -t rpm $(PACKAGE_ARGS) -a x86_64 -v $(RPMVERSION) -p $@ -C $< $(EXTRA_FPM_FLAGS) - [ "$(SIGNING_KEY)" = "" ] || rpmsign --key-id=$(SIGNING_KEY) --resign $@ - -deb: unpackerr_$(VERSION)-$(ITERATION)_amd64.deb -unpackerr_$(VERSION)-$(ITERATION)_amd64.deb: package_build_linux_deb check_fpm - @echo "Building 'deb' package for unpackerr version '$(VERSION)-$(ITERATION)'." - fpm -s dir -t deb $(PACKAGE_ARGS) -a amd64 -v $(VERSION) -p $@ -C $< $(EXTRA_FPM_FLAGS) - [ "$(SIGNING_KEY)" = "" ] || debsigs --default-key="$(SIGNING_KEY)" --sign=origin $@ - -rpm386: unpackerr-$(RPMVERSION)-$(ITERATION).i386.rpm -unpackerr-$(RPMVERSION)-$(ITERATION).i386.rpm: package_build_linux_386_rpm check_fpm - @echo "Building 32-bit 'rpm' package for unpackerr version '$(RPMVERSION)-$(ITERATION)'." - fpm -s dir -t rpm $(PACKAGE_ARGS) -a i386 -v $(RPMVERSION) -p $@ -C $< $(EXTRA_FPM_FLAGS) - [ "$(SIGNING_KEY)" = "" ] || rpmsign --key-id=$(SIGNING_KEY) --resign $@ - -deb386: unpackerr_$(VERSION)-$(ITERATION)_i386.deb -unpackerr_$(VERSION)-$(ITERATION)_i386.deb: package_build_linux_386_deb check_fpm - @echo "Building 32-bit 'deb' package for unpackerr version '$(VERSION)-$(ITERATION)'." - fpm -s dir -t deb $(PACKAGE_ARGS) -a i386 -v $(VERSION) -p $@ -C $< $(EXTRA_FPM_FLAGS) - [ "$(SIGNING_KEY)" = "" ] || debsigs --default-key="$(SIGNING_KEY)" --sign=origin $@ - -rpmarm: unpackerr-$(RPMVERSION)-$(ITERATION).aarch64.rpm -unpackerr-$(RPMVERSION)-$(ITERATION).aarch64.rpm: package_build_linux_arm64_rpm check_fpm - @echo "Building 64-bit ARM8 'rpm' package for unpackerr version '$(RPMVERSION)-$(ITERATION)'." - fpm -s dir -t rpm $(PACKAGE_ARGS) -a arm64 -v $(RPMVERSION) -p $@ -C $< $(EXTRA_FPM_FLAGS) - [ "$(SIGNING_KEY)" = "" ] || rpmsign --key-id=$(SIGNING_KEY) --resign $@ - -debarm: unpackerr_$(VERSION)-$(ITERATION)_arm64.deb -unpackerr_$(VERSION)-$(ITERATION)_arm64.deb: package_build_linux_arm64_deb check_fpm - @echo "Building 64-bit ARM8 'deb' package for unpackerr version '$(VERSION)-$(ITERATION)'." - fpm -s dir -t deb $(PACKAGE_ARGS) -a arm64 -v $(VERSION) -p $@ -C $< $(EXTRA_FPM_FLAGS) - [ "$(SIGNING_KEY)" = "" ] || debsigs --default-key="$(SIGNING_KEY)" --sign=origin $@ - -rpmarmhf: unpackerr-$(RPMVERSION)-$(ITERATION).armhf.rpm -unpackerr-$(RPMVERSION)-$(ITERATION).armhf.rpm: package_build_linux_armhf_rpm check_fpm - @echo "Building 32-bit ARM6/7 HF 'rpm' package for unpackerr version '$(RPMVERSION)-$(ITERATION)'." - fpm -s dir -t rpm $(PACKAGE_ARGS) -a armhf -v $(RPMVERSION) -p $@ -C $< $(EXTRA_FPM_FLAGS) - [ "$(SIGNING_KEY)" = "" ] || rpmsign --key-id=$(SIGNING_KEY) --resign $@ - -debarmhf: unpackerr_$(VERSION)-$(ITERATION)_armhf.deb -unpackerr_$(VERSION)-$(ITERATION)_armhf.deb: package_build_linux_armhf_deb check_fpm - @echo "Building 32-bit ARM6/7 HF 'deb' package for unpackerr version '$(VERSION)-$(ITERATION)'." - fpm -s dir -t deb $(PACKAGE_ARGS) -a armhf -v $(VERSION) -p $@ -C $< $(EXTRA_FPM_FLAGS) - [ "$(SIGNING_KEY)" = "" ] || debsigs --default-key="$(SIGNING_KEY)" --sign=origin $@ - -freebsd_pkg: unpackerr-$(VERSION)_$(ITERATION).amd64.txz unpackerr-$(VERSION)_$(ITERATION).amd64.txz.sig -unpackerr-$(VERSION)_$(ITERATION).amd64.txz: package_build_freebsd check_fpm - @echo "Building 'freebsd pkg' package for unpackerr version '$(VERSION)-$(ITERATION)'." - fpm -s dir -t freebsd $(PACKAGE_ARGS) -a amd64 -v $(VERSION) -p $@ -C $< $(EXTRA_FPM_FLAGS) -unpackerr-$(VERSION)_$(ITERATION).amd64.txz.sig: unpackerr-$(VERSION)_$(ITERATION).amd64.txz - [ "$(SIGNING_KEY)" = "" ] || gpg --local-user "$(SIGNING_KEY)" --output $@ --detach-sig $< - -freebsd386_pkg: unpackerr-$(VERSION)_$(ITERATION).i386.txz unpackerr-$(VERSION)_$(ITERATION).i386.txz.sig -unpackerr-$(VERSION)_$(ITERATION).i386.txz: package_build_freebsd_386 check_fpm - @echo "Building 32-bit 'freebsd pkg' package for unpackerr version '$(VERSION)-$(ITERATION)'." - fpm -s dir -t freebsd $(PACKAGE_ARGS) -a 386 -v $(VERSION) -p $@ -C $< $(EXTRA_FPM_FLAGS) -unpackerr-$(VERSION)_$(ITERATION).i386.txz.sig: unpackerr-$(VERSION)_$(ITERATION).i386.txz - [ "$(SIGNING_KEY)" = "" ] || gpg --local-user "$(SIGNING_KEY)" --output $@ --detach-sig $< - -freebsdarm_pkg: unpackerr-$(VERSION)_$(ITERATION).armhf.txz unpackerr-$(VERSION)_$(ITERATION).armhf.txz.sig -unpackerr-$(VERSION)_$(ITERATION).armhf.txz: package_build_freebsd_arm check_fpm - @echo "Building 32-bit ARM6/7 HF 'freebsd pkg' package for unpackerr version '$(VERSION)-$(ITERATION)'." - fpm -s dir -t freebsd $(PACKAGE_ARGS) -a arm -v $(VERSION) -p $@ -C $< $(EXTRA_FPM_FLAGS) -unpackerr-$(VERSION)_$(ITERATION).armhf.txz.sig: unpackerr-$(VERSION)_$(ITERATION).armhf.txz - [ "$(SIGNING_KEY)" = "" ] || gpg --local-user "$(SIGNING_KEY)" --output $@ --detach-sig $< - -zst: unpackerr-$(VERSION)-$(ITERATION)-x86_64.pkg.tar.zst unpackerr-$(VERSION)-$(ITERATION)-x86_64.pkg.tar.zst.sig -unpackerr-$(VERSION)-$(ITERATION)-x86_64.pkg.tar.zst: package_build_linux_zst check_fpm - @echo "Building 'pacman' package for unpackerr version '$(VERSION)-$(ITERATION)'." - fpm -s dir -t pacman $(PACKAGE_ARGS) -a x86_64 -v $(VERSION) -C $< $(EXTRA_FPM_FLAGS) -unpackerr-$(VERSION)-$(ITERATION)-x86_64.pkg.tar.zst.sig: unpackerr-$(VERSION)-$(ITERATION)-x86_64.pkg.tar.zst - [ "$(SIGNING_KEY)" = "" ] || gpg --local-user "$(SIGNING_KEY)" --output $@ --detach-sig $< - -zstarm: unpackerr-$(VERSION)-$(ITERATION)-aarch64.pkg.tar.zst unpackerr-$(VERSION)-$(ITERATION)-aarch64.pkg.tar.zst.sig -unpackerr-$(VERSION)-$(ITERATION)-aarch64.pkg.tar.zst: package_build_linux_aarch64_zst check_fpm - @echo "Building 64-bit ARM8 'pacman' package for unpackerr version '$(VERSION)-$(ITERATION)'." - fpm -s dir -t pacman $(PACKAGE_ARGS) -a aarch64 -v $(VERSION) -C $< $(EXTRA_FPM_FLAGS) -unpackerr-$(VERSION)-$(ITERATION)-aarch64.pkg.tar.zst.sig: unpackerr-$(VERSION)-$(ITERATION)-aarch64.pkg.tar.zst - [ "$(SIGNING_KEY)" = "" ] || gpg --local-user "$(SIGNING_KEY)" --output $@ --detach-sig $< - -zstarmhf: unpackerr-$(VERSION)-$(ITERATION)-arm7hf.pkg.tar.zst unpackerr-$(VERSION)-$(ITERATION)-arm7hf.pkg.tar.zst.sig -unpackerr-$(VERSION)-$(ITERATION)-arm7hf.pkg.tar.zst: package_build_linux_armhf_zst check_fpm - @echo "Building 32-bit ARM6/7 HF 'pacman' package for unpackerr version '$(VERSION)-$(ITERATION)'." - fpm -s dir -t pacman $(PACKAGE_ARGS) -a armhf -v $(VERSION) -C $< $(EXTRA_FPM_FLAGS) -unpackerr-$(VERSION)-$(ITERATION)-arm7hf.pkg.tar.zst.sig: unpackerr-$(VERSION)-$(ITERATION)-arm7hf.pkg.tar.zst - [ "$(SIGNING_KEY)" = "" ] || gpg --local-user "$(SIGNING_KEY)" --output $@ --detach-sig $< - -# Build an environment that can be packaged for linux. -package_build_linux_rpm: generate readme man linux - # Building package environment for linux. - mkdir -p $@/usr/bin $@/etc/unpackerr $@/usr/share/man/man1 $@/usr/share/doc/unpackerr $@/usr/lib/unpackerr - # Copying the binary, config file, unit file, and man page into the env. - cp unpackerr.amd64.linux $@/usr/bin/unpackerr - cp *.1.gz $@/usr/share/man/man1 - cp examples/unpackerr.conf.example $@/etc/unpackerr/ - cp examples/unpackerr.conf.example $@/etc/unpackerr/unpackerr.conf - cp LICENSE *.html examples/*?.?* $@/usr/share/doc/unpackerr/ - mkdir -p $@/lib/systemd/system - cp init/systemd/unpackerr.service $@/lib/systemd/system/ - [ ! -d "init/linux/rpm" ] || cp -r init/linux/rpm/* $@ - -# Build an environment that can be packaged for linux. -package_build_linux_deb: generate readme man linux - # Building package environment for linux. - mkdir -p $@/usr/bin $@/etc/unpackerr $@/usr/share/man/man1 $@/usr/share/doc/unpackerr $@/usr/lib/unpackerr - # Copying the binary, config file, unit file, and man page into the env. - cp unpackerr.amd64.linux $@/usr/bin/unpackerr - cp *.1.gz $@/usr/share/man/man1 - cp examples/unpackerr.conf.example $@/etc/unpackerr/ - cp examples/unpackerr.conf.example $@/etc/unpackerr/unpackerr.conf - cp LICENSE *.html examples/*?.?* $@/usr/share/doc/unpackerr/ - mkdir -p $@/lib/systemd/system - cp init/systemd/unpackerr.service $@/lib/systemd/system/ - [ ! -d "init/linux/deb" ] || cp -r init/linux/deb/* $@ - -# Build an environment that can be packaged for arch linux. -package_build_linux_zst: generate readme man linux - # Building package environment for linux. - mkdir -p $@/usr/bin $@/etc/unpackerr $@/usr/share/man/man1 \ - $@/usr/share/licenses/unpackerr $@/usr/share/doc/unpackerr $@/var/log/unpackerr - # Copying the binary, config file, unit file, and man page into the env. - cp unpackerr.amd64.linux $@/usr/bin/unpackerr - cp *.1.gz $@/usr/share/man/man1 - cp examples/unpackerr.conf.example $@/etc/unpackerr/ - cp examples/unpackerr.conf.example $@/etc/unpackerr/unpackerr.conf - cp LICENSE $@/usr/share/licenses/unpackerr - cp *.html examples/*?.?* $@/usr/share/doc/unpackerr/ - mkdir -p $@/usr/lib/systemd/system $@/usr/lib/sysusers.d - echo "u unpackerr - \"unpackerr daemon\"" > $@/usr/lib/sysusers.d/unpackerr.conf - chmod 775 $@/var/log/unpackerr $@/usr/share/doc/unpackerr $@/etc/unpackerr - cp init/systemd/unpackerr.service $@/usr/lib/systemd/system/ - [ ! -d "init/linux/zst" ] || cp -r init/linux/zst/* $@ - -package_build_linux_386_deb: package_build_linux_deb linux386 - mkdir -p $@ - cp -r $ /dev/null || (echo "FPM missing. Install FPM: https://fpm.readthedocs.io/en/latest/installing.html" && false) - -################## -##### Extras ##### -################## - -# Run code tests and lint. -test: lint - # Testing. - go test -race -covermode=atomic ./... -lint: generate - # Checking lint. - golangci-lint version - GOOS=linux golangci-lint run - GOOS=freebsd golangci-lint run - GOOS=windows golangci-lint run - -################## -##### Docker ##### -################## - -docker: - init/docker/makedocker.sh diff --git a/init/archlinux/PKGBUILD.template b/init/archlinux/PKGBUILD.template deleted file mode 100644 index 2dfc7a55..00000000 --- a/init/archlinux/PKGBUILD.template +++ /dev/null @@ -1,70 +0,0 @@ -# Maintainer: David Newhall II -# Maintainer: Donald Webster - -pkgname='unpackerr' -pkgver={{VERSION}} -pkgrel={{Iter}} -pkgdesc='{{Desc}}' -arch=('x86_64' 'arm' 'armv6h' 'armv7h' 'aarch64' 'i686' 'pentium4') -url='https://unpackerr.zip' -license=('MIT') -makedepends=('go' 'gzip') -optdepends=( - 'transmission-cli: torrent downloader (CLI and daemon)' - 'transmission-gtk: torrent downloader (GTK+)' - 'transmission-qt: torrent downloader (Qt)' - 'deluge: torrent downloader' - 'rtorrent: torrent downloader' -) - -source=("${pkgname}-${pkgver}.tar.gz::https://github.com/Unpackerr/unpackerr/archive/v${pkgver}.tar.gz") -sha512sums=('{{SHA}}') - -backup=("etc/${pkgname}/${pkgname}.conf") - -prepare(){ - cd "$pkgname-$pkgver" - mkdir -p build/ -} - -build() { - cd "$pkgname-$pkgver" - - export GOFLAGS="-buildmode=pie -trimpath -modcacherw" - LDFLAGS="-w -s -X 'golift.io/version.Branch=main (${sha512sums[0]:0:11})' \ - -X golift.io/version.BuildDate=$(date -u +%Y-%m-%dT%H:%M:00Z) \ - -X golift.io/version.BuildUser=$(whoami || echo unknown) \ - -X golift.io/version.Revision=${pkgrel} \ - -X golift.io/version.Version=${pkgver}" - - go build -o unpackerr -ldflags "$LDFLAGS" . - go run github.com/davidnewhall/md2roff@v0.0.1 --manual unpackerr --version "${pkgver}" --date "${DATE}" README.md - go run github.com/davidnewhall/md2roff@v0.0.1 --manual unpackerr --version "${pkgver}" --date "${DATE}" examples/MANUAL.md - gzip -9 examples/MANUAL - mv examples/MANUAL.gz "${pkgname}.1.gz" -} - -package() { - cd "${srcdir}/${pkgname}-${pkgver}" - - # Directories. - install -d -m 775 "${pkgdir}/usr/share/"{licenses,doc}"/${pkgname}" "${pkgdir}/etc/${pkgname}" - - # Install the binary - install -D -m 755 "${pkgname}" "${pkgdir}/usr/bin/${pkgname}" - - # Install configuration file(s). - install -D -m 644 "examples/${pkgname}.conf.example" "${pkgdir}/etc/${pkgname}/${pkgname}.conf" - install -D -m 644 "examples/${pkgname}.conf.example" "${pkgdir}/etc/${pkgname}/${pkgname}.conf.example" - - # License, documentation, manual. - install -D -m 644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" - install -D -m 644 ./*.html examples/* "${pkgdir}/usr/share/doc/${pkgname}/" - install -D -m 644 "${pkgname}.1.gz" "${pkgdir}/usr/share/man/man1/${pkgname}.1.gz" - - # Install the systemd service unit and system user account. - install -D -m 644 "init/systemd/${pkgname}.service" "${pkgdir}/usr/lib/systemd/system/${pkgname}.service" - echo "u ${pkgname} - \"${pkgname} daemon\"" > "${pkgname}.sysusers" - install -D -m 644 "${pkgname}.sysusers" "${pkgdir}/usr/lib/sysusers.d/${pkgname}.conf" - rm "${pkgname}.sysusers" -} diff --git a/init/archlinux/README.md b/init/archlinux/README.md deleted file mode 100644 index 14b0580d..00000000 --- a/init/archlinux/README.md +++ /dev/null @@ -1,2 +0,0 @@ -This template file is built by GitHub Actions and uploaded to the -[AUR repo](https://aur.archlinux.org/packages/unpackerr). diff --git a/init/archlinux/SRCINFO.template b/init/archlinux/SRCINFO.template deleted file mode 100644 index 0808a96f..00000000 --- a/init/archlinux/SRCINFO.template +++ /dev/null @@ -1,25 +0,0 @@ -pkgbase = unpackerr - pkgdesc = {{Desc}} - pkgver = {{VERSION}} - pkgrel = 1 - url = https://unpackerr.zip - arch = x86_64 - arch = arm - arch = armv6h - arch = armv7h - arch = aarch64 - arch = i686 - arch = pentium4 - license = MIT - makedepends = go - makedepends = gzip - optdepends = transmission-cli: torrent downloader (CLI and daemon) - optdepends = transmission-gtk: torrent downloader (GTK+) - optdepends = transmission-qt: torrent downloader (Qt) - optdepends = deluge: torrent downloader - optdepends = rtorrent: torrent downloader - backup = etc/unpackerr/unpackerr.conf - source = unpackerr-{{VERSION}}.tar.gz::{{SOURCE_PATH}} - sha512sums = {{SHA}} - -pkgname = unpackerr diff --git a/init/archlinux/aur-deploy.sh b/init/archlinux/aur-deploy.sh deleted file mode 100644 index 270ac77b..00000000 --- a/init/archlinux/aur-deploy.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash -x - -# Deploys a new aur PKGBUILD file to an arch linux aur github repo. -# Run by GitHub Actions when a new release is created on GitHub. - -source settings.sh - -SOURCE_PATH="https://github.com/Unpackerr/unpackerr/archive/v${VERSION}.tar.gz" -echo "==> Using URL: $SOURCE_PATH" -SHA=$(curl -sL "$SOURCE_PATH" | sha512sum | awk '{print $1}') - -push_it() { - pushd release_repo - git add . - git config --global user.email "unpackerr@github.releaser" - git config --global user.name "unpackerr-github-releaser" - git commit -m "Update unpackerr on Release: v${VERSION}-${ITERATION}" - git push - popd - rm -rf release_repo -} - -set -e - -mkdir -p "${HOME}/.ssh" -KEY_FILE=$(mktemp -u "${HOME}/.ssh/aur-deploy-key.XXXXX") -echo "${DEPLOY_KEY}" > "${KEY_FILE}" -chmod 600 "${KEY_FILE}" -# Configure ssh to use this secret. -export GIT_SSH_COMMAND="ssh -i ${KEY_FILE} -o 'StrictHostKeyChecking no'" - -rm -rf release_repo -git clone aur@aur.archlinux.org:unpackerr.git release_repo - -sed -e "s/{{VERSION}}/${VERSION}/g" \ - -e "s/{{Iter}}/${ITERATION}/g" \ - -e "s/{{SHA}}/${SHA}/g" \ - -e "s/{{Desc}}/${DESC}/g" \ - init/archlinux/PKGBUILD.template | tee release_repo/PKGBUILD - -sed -e "s/{{VERSION}}/${VERSION}/g" \ - -e "s/{{Iter}}/${ITERATION}/g" \ - -e "s/{{SHA}}/${SHA}/g" \ - -e "s/{{Desc}}/${DESC}/g" \ - -e "s%{{SOURCE_PATH}}%${SOURCE_PATH}%g" \ - init/archlinux/SRCINFO.template | tee release_repo/.SRCINFO - -[ "$1" != "" ] || push_it diff --git a/init/docker/Dockerfile b/init/docker/Dockerfile index 048117e1..eddf92da 100644 --- a/init/docker/Dockerfile +++ b/init/docker/Dockerfile @@ -1,62 +1,10 @@ -FROM golang:1-alpine AS builder +FROM alpine:3.22 -WORKDIR /src -COPY main.go settings.sh go.mod go.sum ./ -RUN go mod download -COPY pkg pkg -COPY examples examples -RUN go generate ./... +RUN apk add --no-cache ca-certificates openssl tzdata -ARG TARGETOS -ARG TARGETARCH -ARG BUILD_DATE -ARG BRANCH -ARG VERSION -ARG ITERATION -ARG COMMIT - -ENV GOFLAGS="-trimpath -mod=readonly -modcacherw" -ENV GOOS=${TARGETOS} -ENV GOARCH=${TARGETARCH} - -RUN go build -o /tmp/unpackerr -tags osusergo,netgo \ - -ldflags "-w -s -X \"golift.io/version.Branch=${BRANCH} (${COMMIT})\" \ - -X \"golift.io/version.BuildDate=${BUILD_DATE}\" \ - -X \"golift.io/version.BuildUser=docker\" \ - -X \"golift.io/version.Revision=${ITERATION}\" \ - -X \"golift.io/version.Version=${VERSION}\"" - -FROM alpine - -ARG TARGETOS -ARG TARGETARCH -ARG BUILD_DATE -ARG COMMIT -ARG VERSION -ARG ITERATION -ARG LICENSE=MIT -ARG SOURCE_URL=http://github.com/golift/application-builder -ARG DESC=application-builder -ARG VENDOR=golift -ARG AUTHOR=golift -# Build-time metadata as defined at https://github.com/opencontainers/image-spec/blob/master/annotations.md -LABEL org.opencontainers.image.created="${BUILD_DATE}" \ - org.opencontainers.image.title="Unpackerr" \ - org.opencontainers.image.documentation="https://unpackerr.zip" \ - org.opencontainers.image.description="${DESC}" \ - org.opencontainers.image.url="${SOURCE_URL}" \ - org.opencontainers.image.revision="${COMMIT}" \ - org.opencontainers.image.source="${SOURCE_URL}" \ - org.opencontainers.image.vendor="${VENDOR}" \ - org.opencontainers.image.authors="${AUTHOR}" \ - org.opencontainers.image.architecture="${TARGETOS} ${TARGETARCH}" \ - org.opencontainers.image.licenses="${LICENSE}" \ - org.opencontainers.image.version="${VERSION}-${ITERATION}" - -COPY --from=builder /tmp/unpackerr /unpackerr -# Make sure we have an ssl cert chain and timezone data. -RUN apk add --no-cache openssl tzdata +ARG TARGETPLATFORM +COPY ${TARGETPLATFORM}/unpackerr /unpackerr ENV TZ=UTC -ENTRYPOINT [ "/unpackerr" ] +ENTRYPOINT ["/unpackerr"] diff --git a/init/docker/hooks/build b/init/docker/hooks/build deleted file mode 100755 index d449bec7..00000000 --- a/init/docker/hooks/build +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash - -# This file is designed to run as build hook for Automated Builds on Docker.com. -# This always run local to the Dockerfile folder, so the path is ../.. -pushd ../.. -source settings.sh - -docker buildx inspect - -if ! (docker buildx inspect | grep -Eq 'docker-container'); then - echo "Creating docker builder instance" - docker buildx create --use -fi - -read -r -d '' BUILD_ARGS << EOF ---build-arg "BUILD_DATE=${DATE}" \ ---build-arg "BUILD_FLAGS=${BUILD_FLAGS}" \ ---build-arg "COMMIT=${COMMIT}" \ ---build-arg "BRANCH=${BRANCH}" \ ---build-arg "VERSION=${VERSION}" \ ---build-arg "ITERATION=${ITERATION}" \ ---build-arg "LICENSE=${LICENSE}" \ ---build-arg "DESC=${DESC}" \ ---build-arg "VENDOR=${VENDOR}" \ ---build-arg "AUTHOR=${MAINT}" \ ---build-arg "SOURCE_URL=${SOURCE_URL}" \ ---file ${DOCKERFILE_PATH} . -EOF - -echo "Build Args: ${BUILD_ARGS}" -eval "docker buildx build --load --tag current:amd64 --platform linux/amd64 $BUILD_ARGS" -eval "docker buildx build --load --tag current:arm64 --platform linux/arm64/v8 $BUILD_ARGS" -eval "docker buildx build --load --tag current:arm --platform linux/arm $BUILD_ARGS" -echo "Done Building." - -TAGS="$SOURCE_BRANCH" -if [ "v$VERSION" = "$SOURCE_BRANCH" ]; then - TAGS="$VERSION" - - echo $SOURCE_BRANCH | grep -q -- - - if [ "$?" = "1" ]; then - # tag does not contain a dash, so assume it's a prod tag. - TAGS="$TAGS latest unstable $(echo $VERSION | cut -d. -f1,2) $(echo $VERSION | cut -d. -f1)" - fi -fi - -for tag in $TAGS; do - echo "Pushing tag: $DOCKER_REPO:$tag" - eval "docker buildx build --push --tag $DOCKER_REPO:$tag --platform linux/amd64,linux/arm64/v8,linux/arm $BUILD_ARGS" -done diff --git a/init/docker/hooks/pre_build b/init/docker/hooks/pre_build deleted file mode 100644 index 35f818b3..00000000 --- a/init/docker/hooks/pre_build +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -# This upgrades the docker client on the Docker Cloud server to a version -# that contains the `docker manifest` command, so we can build with QEMU. - -docker run --rm --privileged multiarch/qemu-user-static --reset -p yes diff --git a/init/docker/hooks/push b/init/docker/hooks/push deleted file mode 100644 index 497e4c09..00000000 --- a/init/docker/hooks/push +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -#empty \ No newline at end of file diff --git a/init/docker/makedocker.sh b/init/docker/makedocker.sh deleted file mode 100755 index 914343b9..00000000 --- a/init/docker/makedocker.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -# This is invoked by the Makefile to create a simple docker image ready to go. - -source settings.sh - -docker buildx build --load --pull --tag unpackerr \ - --platform linux/amd64 \ - --build-arg "BUILD_DATE=${DATE}" \ - --build-arg "COMMIT=${COMMIT}" \ - --build-arg "BRANCH=${BRANCH}" \ - --build-arg "VERSION=${VERSION}" \ - --build-arg "ITERATION=${ITERATION}" \ - --build-arg "LICENSE=${LICENSE}" \ - --build-arg "DESC=${DESC}" \ - --build-arg "VENDOR=${VENDOR}" \ - --build-arg "AUTHOR=${MAINT}" \ - --build-arg "SOURCE_URL=${SOURCE_URL}" \ - --file init/docker/Dockerfile . \ No newline at end of file diff --git a/init/install.sh b/init/install.sh index 657ba507..359a1e32 100644 --- a/init/install.sh +++ b/init/install.sh @@ -44,11 +44,14 @@ fi if [[ $ARCH = x86_64 ]] || [[ $ARCH = amd64 ]]; then ARCH="x86_64|amd64" elif [[ $ARCH = *386* ]] || [[ $ARCH = *686* ]]; then - ARCH="i386" + # Deb: i386. Arch pkg.tar.zst: i686. + ARCH="i386|i686" elif [[ $ARCH = *arm64* ]] || [[ $ARCH = *armv8* ]] || [[ $ARCH = *aarch64* ]]; then - ARCH="arm64" + # Deb: arm64. RPM/Arch: aarch64. + ARCH="arm64|aarch64" elif [[ $ARCH = *armv6* ]] || [[ $ARCH = *armv7* ]]; then - ARCH="armhf" + # Deb/old rpm/freebsd: armhf. RPM: armv7hl. Arch: armv7h. Old zst: arm7hf. + ARCH="armv7hl|armv7h|armhf|arm7hf|armv7" else echo "${P} [ERROR] Unknown Architecture: ${ARCH}" echo "${P} $(uname -a) | $(head -n 1 /etc/issue): ${ID}" diff --git a/init/macos/Info.plist.tmpl b/init/macos/Info.plist.tmpl new file mode 100644 index 00000000..ff65d4e7 --- /dev/null +++ b/init/macos/Info.plist.tmpl @@ -0,0 +1,42 @@ + + + + + CFBundleName + Unpackerr + CFBundleIdentifier + io.golift.unpackerr + CFBundleInfoDictionaryVersion + 6.0 + CFBundleVersion + {{ .Env.REVISION }} + CFBundleShortVersionString + {{ .RawVersion }} + LSMinimumSystemVersion + 13.0 + CFBundleExecutable + {{ .BinaryName }} + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSPrincipalClass + NSApplication + LSUIElement + + CFBundleIconFile + icon.icns + CFBundleDisplayName + Unpackerr + NSHumanReadableCopyright + © Go Lift (https://golift.io) + LSEnvironment + + USEGUI + true + + NSHighResolutionCapable + + + diff --git a/init/macos/Unpackerr.app/Contents/Info.plist b/init/macos/Unpackerr.app/Contents/Info.plist index b7ab2178..d1192939 100644 --- a/init/macos/Unpackerr.app/Contents/Info.plist +++ b/init/macos/Unpackerr.app/Contents/Info.plist @@ -34,7 +34,7 @@ Unpackerr NSHumanReadableCopyright - © 2023 Go Lift (https://golift.io) + © 2026 Go Lift (https://golift.io) LSEnvironment diff --git a/init/macos/entitlements.plist b/init/macos/entitlements.plist new file mode 100644 index 00000000..4ad44e40 --- /dev/null +++ b/init/macos/entitlements.plist @@ -0,0 +1,12 @@ + + + + + com.apple.security.network.client + + com.apple.security.network.server + + com.apple.security.files.user-selected.read-write + + + diff --git a/init/macos/makedmg.sh b/init/macos/makedmg.sh deleted file mode 100755 index 7a8bb90f..00000000 --- a/init/macos/makedmg.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash -# This file builds a standard DMG installer for macOS. -# This only works on macOS. -########################################### - -set -e -o pipefail - -# If we are running in GH Actions, make a new keychain and import the certificate. -if [ -n "$APPLE_SIGNING_KEY" ]; then - KEYCHAIN="ios-build.keychain" - - echo "==> Creating new keychain: $KEYCHAIN" - security create-keychain -p secret $KEYCHAIN - - echo "==> Importing certificate into ${KEYCHAIN}" - echo "${APPLE_SIGNING_KEY}" | base64 -d | \ - security import /dev/stdin -P "" -f pkcs12 -k $KEYCHAIN -T /usr/bin/codesign - - echo "==> Unlocking keychain ${KEYCHAIN}" - security unlock-keychain -p secret $KEYCHAIN - - echo "==> Increase keychain unlock timeout to 1 hour." - security set-keychain-settings -lut 3600 $KEYCHAIN - - security set-key-partition-list -S apple-tool:,apple: -s -k secret $KEYCHAIN - - echo "==> Add keychain to keychain-list" - security list-keychains -s $KEYCHAIN -fi - -echo "==> Signing App." -gon init/macos/sign.json - -# Creating non-notarized DMG. -mkdir -p release -hdiutil create release/Unpackerr.dmg -srcfolder Unpackerr.app -ov - -echo "==> Notarizing DMG." -gon init/macos/notarize.json - -echo "==> Finished." diff --git a/init/macos/notarize.json b/init/macos/notarize.json deleted file mode 100644 index 7e268fd6..00000000 --- a/init/macos/notarize.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "notarize": [{ - "path": "release/Unpackerr.dmg", - "bundle_id": "io.golift.unpackerr", - "staple": true - }] -} \ No newline at end of file diff --git a/init/macos/notarize.sh b/init/macos/notarize.sh deleted file mode 100755 index 02c6bff2..00000000 --- a/init/macos/notarize.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env bash - -## This doesn't work in CI/CD because of the AppleScript. -# Must be run on a real Mac with a real apple ID and password. -# AC_PASSWORD and AC_USERNAME variables are required to be set. -# The only part that doesn't work in CI/CD currently is the applescript. - -# Download latest release. -echo "==> Getting latest release." -URL=$(curl -s https://api.github.com/repos/Unpackerr/unpackerr/releases/latest | \ - jq -r '.assets[] | select(.name == "Unpackerr.dmg") | .browser_download_url') -echo "==> Downloading: $URL" -curl -sSLo /tmp/Unpackerr.dmg "$URL" -echo "==> Mounting Unpackerr.dmg to /Volumes/UnpackerrRelease" -hdiutil attach -readonly -mountpoint /Volumes/UnpackerrRelease /tmp/Unpackerr.dmg - -# Create r/w image with latest release app as source. -echo "==> Creating intermediate image: pack.temp.dmg." -rm -f pack.temp.dmg -hdiutil create -srcfolder "/Volumes/UnpackerrRelease/Unpackerr.app" -volname "Unpackerr" -fs HFS+ \ - -fsargs "-c c=64,a=16,e=16" -format UDRW -size 200000k pack.temp.dmg - -echo "==> Unmounting /Volumes/UnpackerrRelease and /Volumes/UnpackerrIntermediate (may not be mounted)." -hdiutil detach "/Volumes/UnpackerrIntermediate" -hdiutil detach "/Volumes/UnpackerrRelease" -sleep 1 - -echo "==> Mounting pack.temp.dmg to /Volumes/UnpackerrIntermediate" -hdiutil attach -mountpoint /Volumes/UnpackerrIntermediate -readwrite -noverify -noautoopen "pack.temp.dmg" | \ - egrep '^/dev/' | sed 1q | awk '{print $1}' - -# Create content. -sleep 1 -echo "==> Copying background image." -mkdir "/Volumes/UnpackerrIntermediate/.background" -cp -r init/macos/background.png "/Volumes/UnpackerrIntermediate/.background/Unpackerr.png" - -echo "==> Running AppleScript to build custom DMG." -echo ' - tell application "Finder" - tell disk "'UnpackerrIntermediate'" - open - set current view of container window to icon view - set toolbar visible of container window to false - set statusbar visible of container window to false - set the bounds of container window to {400, 100, 1320, 600} - set theViewOptions to the icon view options of container window - set arrangement of theViewOptions to not arranged - set icon size of theViewOptions to 256 - set background picture of theViewOptions to file ".background:'Unpackerr.png'" - make new alias file at container window to POSIX file "/Applications" with properties {name:"Applications"} - set position of item "'Unpackerr.app'" of container window to {0, 0} - set position of item "Applications" of container window to {600, 0} - update without registering applications - delay 1 - close - end tell - end tell -' | osascript - -sleep 1 -# Finalize. -echo "==> Finalizing DMG." -chmod -Rf go-w /Volumes/UnpackerrIntermediate - -sleep 1 -echo "==> Unmounting /Volumes/UnpackerrIntermediate." -hdiutil detach /Volumes/UnpackerrIntermediate - -sleep 1 -echo "==> Converting DMG to compressed read only." -mkdir -p release -rm -f "release/Unpackerr.dmg" -hdiutil convert "pack.temp.dmg" -format UDZO -imagekey zlib-level=9 -o "release/Unpackerr.dmg" -rm -f pack.temp.dmg - -echo "==> Notarizing DMG." -gon init/macos/notarize.json -echo "==> Finished!" -ls -l release/Unpackerr.dmg \ No newline at end of file diff --git a/init/macos/sign.json b/init/macos/sign.json deleted file mode 100644 index 4c9435d5..00000000 --- a/init/macos/sign.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "source": ["Unpackerr.app"], - "bundle_id": "io.golift.unpackerr", - "sign": { "application_identity" : "1AE9653F18114B23927AF7FA6624EF74F95A4544" } -} - diff --git a/init/systemd/unpackerr.tmpfiles b/init/systemd/unpackerr.tmpfiles new file mode 100644 index 00000000..b57e7c1a --- /dev/null +++ b/init/systemd/unpackerr.tmpfiles @@ -0,0 +1,2 @@ +# systemd-tmpfiles.d — log dir for User=unpackerr (see unpackerr.service). +d /var/log/unpackerr 0755 unpackerr unpackerr - diff --git a/init/windows/signexe.sh b/init/windows/signexe.sh index 894a2ad8..8a355c0f 100755 --- a/init/windows/signexe.sh +++ b/init/windows/signexe.sh @@ -3,24 +3,29 @@ set -e -o pipefail # Authenticode-sign a Windows PE via golift/codesign (YubiKey-backed signerd). -# GitHub Actions uses golift/codesign@v1 after `make release WINDOWS_ZIP=0`. -# This script is for local `make windows` when CODESIGN_URL is set (SSH tunnel -# or a configured CLI). +# GoReleaser calls this from builds.hooks.post on windows binaries, after the +# CLI is installed into a temp GOBIN (never /usr/bin/codesign). # -# On macOS, never call /usr/bin/codesign (Apple's tool). Prefer CODESIGN_BIN, -# "$(go env GOPATH)/bin/codesign", then any other `codesign` on PATH. +# Local snapshots skip when CODESIGN_URL is unset. GitHub Actions must fail +# closed — the release contract is an Authenticode-signed Windows binary. +# +# Prefer CODESIGN_BIN, then GOBIN/codesign, then GOPATH/bin, then PATH +# entries that are not Apple's /usr/bin/codesign. function pick_codesign() { if [ -n "${CODESIGN_BIN:-}" ]; then echo "${CODESIGN_BIN}" return fi + if [ -n "${GOBIN:-}" ] && [ -x "${GOBIN}/codesign" ]; then + echo "${GOBIN}/codesign" + return + fi gopath="$(go env GOPATH 2>/dev/null || true)" if [ -n "${gopath}" ] && [ -x "${gopath}/bin/codesign" ]; then echo "${gopath}/bin/codesign" return fi - # Apple ships /usr/bin/codesign. Skip it; any other PATH hit is the CLI. while IFS= read -r p; do case "$p" in /usr/bin/codesign|/bin/codesign) continue ;; @@ -33,15 +38,15 @@ function pick_codesign() { function sign() { if [ -z "${CODESIGN_URL:-}" ]; then + if [ -n "${GITHUB_ACTIONS:-}" ]; then + echo "CODESIGN_URL unset; refusing to ship an unsigned Windows binary in CI" >&2 + exit 1 + fi echo "Skipped signing ${FILE} (CODESIGN_URL unset) .." >&2 exit 0 fi bin="$(pick_codesign)" || { - if [ -n "${GITHUB_ACTIONS:-}" ]; then - echo "Skipped signing ${FILE} (codesign CLI not on PATH; Action signs later) .." >&2 - exit 0 - fi echo "CODESIGN_URL is set but golift codesign CLI not found (set CODESIGN_BIN)" >&2 exit 1 } diff --git a/pkg/unpackerr/tray.go b/pkg/unpackerr/tray.go index 8a74f725..db1106c5 100644 --- a/pkg/unpackerr/tray.go +++ b/pkg/unpackerr/tray.go @@ -31,6 +31,17 @@ func (u *Unpackerr) startTray() { systray.Run(u.readyTray, u.exitTray) } +// showTrayMenu pops the tray menu. energye/systray does not attach the menu to +// the status item the way getlantern did, so a click does nothing unless we +// call ShowMenu. Windows left-click has the same requirement. +func showTrayMenu(menu systray.IMenu) { + if menu == nil { + return + } + + _ = menu.ShowMenu() +} + func (u *Unpackerr) exitTray() { u.Stop() // stop and wait for extractions. // because systray wants to control the exit code? no.. @@ -41,6 +52,8 @@ func (u *Unpackerr) exitTray() { func (u *Unpackerr) readyTray() { systray.SetTemplateIcon(bindata.SystrayIcon, bindata.SystrayIcon) systray.SetTooltip("Unpackerr" + " v" + version.Version) + systray.SetOnClick(showTrayMenu) + systray.SetOnRClick(showTrayMenu) u.makeChannels() u.menu["info"].Disable() diff --git a/settings.sh b/settings.sh deleted file mode 100644 index 65c6fc21..00000000 --- a/settings.sh +++ /dev/null @@ -1,33 +0,0 @@ - -MAINT="David Newhall II " -DESC="Extracts downloads so Radarr, Sonarr, Lidarr or Readarr may import them." -LICENSE="MIT" -# Used for source links in package metadata and docker labels. -SOURCE_URL="https://github.com/Unpackerr/unpackerr" -VENDOR="Go Lift " -export MAINT DESC LICENSE SOURCE_URL VENDOR - -DATE="$(date -u +%Y-%m-%dT%H:%M:00Z)" -VERSION=$(git tag --sort version:refname | tail -n1 | tr -d v) -[ "$VERSION" != "" ] || VERSION=development -# This produces a 0 in some environments (like Homebrew), but it's only used for packages. -ITERATION=$(git rev-list --count --all || echo 0) -COMMIT="$(git rev-parse --short HEAD || echo 0)" -GIT_BRANCH="$(git rev-parse --abbrev-ref HEAD || echo unknown)" -BRANCH="${GIT_BRANCH:-${GITHUB_REF_NAME}}" -export DATE VERSION ITERATION COMMIT BRANCH - -### Optional ### - -# Import this signing key only if it's in the keyring. -if gpg --list-keys 2>/dev/null | grep -q B93DD66EF98E54E2EAE025BA0166AD34ABC5A57C && debsigs -h > /dev/null; then - export SIGNING_KEY=B93DD66EF98E54E2EAE025BA0166AD34ABC5A57C -fi - -# Make sure Docker builds work locally. -# These do not affect automated builds, just allow the docker build scripts to run from a local clone. -[ -n "$SOURCE_BRANCH" ] || export SOURCE_BRANCH=$BRANCH -[ -n "$DOCKER_TAG" ] || export DOCKER_TAG=$(echo $SOURCE_BRANCH | sed 's/^v*\([0-9].*\)/\1/') -[ -n "$DOCKER_REPO" ] || export DOCKER_REPO="golift/unpackerr" -[ -n "$IMAGE_NAME" ] || export IMAGE_NAME="${DOCKER_REPO}:${DOCKER_TAG}" -[ -n "$DOCKERFILE_PATH" ] || export DOCKERFILE_PATH="init/docker/Dockerfile"