From cda76671e59cde86b2053a72e7797fd74791a94d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 31 Mar 2026 14:50:30 +0000 Subject: [PATCH 01/14] ci: use macos-15 GitHub-hosted runner Replace deprecated macos-11 with macos-15. Bump asdf cache keys so builds do not reuse caches from the old image. Co-authored-by: Dominic Letz --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6298ca3..4197e15 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,14 +7,14 @@ env: jobs: macos: - runs-on: macos-11 + runs-on: macos-15 steps: - name: asdf cache uses: actions/cache@v3 id: asdf-cache with: path: /Users/runner/.asdf - key: macos-otp-${{ env.OTP_VERSION }} + key: macos-15-otp-${{ env.OTP_VERSION }} - name: "Installing Erlang" if: steps.asdf-cache.outputs.cache-hit != 'true' @@ -34,7 +34,7 @@ jobs: uses: actions/cache/save@v3 with: path: /Users/runner/.asdf - key: macos-otp-${{ env.OTP_VERSION }} + key: macos-15-otp-${{ env.OTP_VERSION }} - uses: actions/checkout@v3 - name: "Create keychain" From bb297488f344b4a8a837674538b7d9ff48a5f938 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 1 Apr 2026 08:36:05 +0000 Subject: [PATCH 02/14] fix: resolve Mix warning and Credo nesting issues - Replace Mix.ensure_application!/1 with Application.ensure_all_started/1 (Mix API no longer public; :public_key is in extra_applications) - Extract maybe_rewrite_dep_to_approot/4 to flatten release/1 callback nesting - Extract import_webkit_entry/2 to satisfy Credo in import_webkit/2 Co-authored-by: Dominic Letz --- lib/package/linux.ex | 20 ++++++++++++-------- lib/package/macos.ex | 14 +++++++------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/lib/package/linux.ex b/lib/package/linux.ex index a4681dc..ad05ede 100644 --- a/lib/package/linux.ex +++ b/lib/package/linux.ex @@ -80,14 +80,7 @@ defmodule Desktop.Deployment.Package.Linux do files = wildcard(Path.dirname(libwebkit), "#{basename}/*") for file <- files do - if File.dir?(file) do - File.mkdir_p!(Path.join([priv(pkg), "libwebkit2gtk", Path.basename(file)])) - - for subfile <- wildcard(file, "*"), - do: priv_import!(pkg, subfile, extra_path: ["libwebkit2gtk/#{Path.basename(file)}"]) - else - priv_import!(pkg, file, extra_path: ["libwebkit2gtk"]) - end + import_webkit_entry(pkg, file) end redirection = @@ -99,6 +92,17 @@ defmodule Desktop.Deployment.Package.Linux do end end + defp import_webkit_entry(%Package{} = pkg, file) do + if File.dir?(file) do + File.mkdir_p!(Path.join([priv(pkg), "libwebkit2gtk", Path.basename(file)])) + + for subfile <- wildcard(file, "*"), + do: priv_import!(pkg, subfile, extra_path: ["libwebkit2gtk/#{Path.basename(file)}"]) + else + priv_import!(pkg, file, extra_path: ["libwebkit2gtk"]) + end + end + defp import_libgstreamer_modules(%Package{} = pkg, deps) do libgst = Enum.find(deps, fn lib -> String.starts_with?(Path.basename(lib), "libgstreamer") end) diff --git a/lib/package/macos.ex b/lib/package/macos.ex index 6dcd587..2189165 100644 --- a/lib/package/macos.ex +++ b/lib/package/macos.ex @@ -93,11 +93,7 @@ defmodule Desktop.Deployment.Package.MacOS do end for bin <- find_binaries(root) do - rewrite_deps(bin, fn dep -> - if should_rewrite?(bin, dep) do - rewrite_to_approot(pkg, bin, dep, root) - end - end) + rewrite_deps(bin, &maybe_rewrite_dep_to_approot(pkg, bin, &1, root)) end developer_id = find_developer_id() @@ -336,6 +332,10 @@ defmodule Desktop.Deployment.Package.MacOS do )) end + defp maybe_rewrite_dep_to_approot(pkg, bin, dep, root) do + if should_rewrite?(bin, dep), do: rewrite_to_approot(pkg, bin, dep, root) + end + defp rewrite_to_approot(pkg, bin, dep, root) do location = if String.contains?(dep, ".framework/") do @@ -416,9 +416,9 @@ defmodule Desktop.Deployment.Package.MacOS do @friendly_attribute {2, 5, 4, 3} def locate_uid(pem_filename) do cert = File.read!(pem_filename) - # Test for missing public_key application + # Ensure :public_key is started (see extra_applications in mix.exs) # ref https://elixirforum.com/t/nerves-key-hub-mix-tasks-fail-because-of-missing-pubkey-pem-module/62821/2 - Mix.ensure_application!(:public_key) + Application.ensure_all_started(:public_key) cert_der = List.keyfind!(:public_key.pem_decode(cert), :Certificate, 0) :public_key.der_decode(:Certificate, elem(cert_der, 1)) From eb754f7d0414122953c1064a56a03f45ec8811ec Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Mon, 13 Jul 2026 14:45:00 +0200 Subject: [PATCH 03/14] ci: install asdf from release binary on macos-15 asdf removed the bash-based asdf.sh from master in April 2026, which broke fresh CI installs after the runner cache key changed. Co-authored-by: Cursor --- .github/workflows/test.yml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4197e15..4fd169d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,7 @@ name: "test mix.create_keychain" on: ["push", "pull_request"] env: + ASDF_VERSION: v0.18.1 OTP_VERSION: 24.3.4.15 ELIXIR_VERSION: 1.14.5 ELIXIR_VARIANT: -otp-24 @@ -14,13 +15,20 @@ jobs: id: asdf-cache with: path: /Users/runner/.asdf - key: macos-15-otp-${{ env.OTP_VERSION }} + key: macos-15-asdf-${{ env.ASDF_VERSION }}-otp-${{ env.OTP_VERSION }} - name: "Installing Erlang" if: steps.asdf-cache.outputs.cache-hit != 'true' run: | - git clone https://github.com/asdf-vm/asdf.git ~/.asdf - . $HOME/.asdf/asdf.sh + mkdir -p ~/.asdf/bin ~/.asdf/shims + case $(uname -m) in + arm64) ASDF_ARCH=darwin-arm64 ;; + x86_64) ASDF_ARCH=darwin-amd64 ;; + *) echo "Unsupported architecture: $(uname -m)"; exit 1 ;; + esac + curl -sSL "https://github.com/asdf-vm/asdf/releases/download/${{ env.ASDF_VERSION }}/asdf-${{ env.ASDF_VERSION }}-${ASDF_ARCH}.tar.gz" | tar -xz -C ~/.asdf/bin + export ASDF_DATA_DIR="$HOME/.asdf" + export PATH="$ASDF_DATA_DIR/shims:$ASDF_DATA_DIR/bin:$PATH" asdf plugin add erlang asdf plugin add elixir echo "erlang ${{ env.OTP_VERSION }}" >> .tool-versions @@ -34,7 +42,7 @@ jobs: uses: actions/cache/save@v3 with: path: /Users/runner/.asdf - key: macos-15-otp-${{ env.OTP_VERSION }} + key: macos-15-asdf-${{ env.ASDF_VERSION }}-otp-${{ env.OTP_VERSION }} - uses: actions/checkout@v3 - name: "Create keychain" @@ -43,7 +51,8 @@ jobs: run: | echo "erlang ${{ env.OTP_VERSION }}" > .tool-versions echo "elixir ${{ env.ELIXIR_VERSION }}${{ env.ELIXIR_VARIANT }}" >> .tool-versions - . $HOME/.asdf/asdf.sh + export ASDF_DATA_DIR="$HOME/.asdf" + export PATH="$ASDF_DATA_DIR/shims:$ASDF_DATA_DIR/bin:$PATH" mix local.hex --force mix local.rebar --force mix deps.get From 08d015cf13c582146f4a43025d79ee35faa7e55c Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Mon, 13 Jul 2026 14:51:50 +0200 Subject: [PATCH 04/14] fix: unlock imported macOS signing key before identity lookup macOS 15 requires set-key-partition-list after PEM import before find-identity -p codesigning can see the Developer ID certificate. Co-authored-by: Cursor --- lib/mix/tasks/create_keychain.ex | 2 +- lib/package/macos.ex | 23 ++++++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/mix/tasks/create_keychain.ex b/lib/mix/tasks/create_keychain.ex index af39dba..ae46e0d 100644 --- a/lib/mix/tasks/create_keychain.ex +++ b/lib/mix/tasks/create_keychain.ex @@ -41,7 +41,7 @@ defmodule Mix.Tasks.Desktop.CreateKeychain do file = "tmp.pem" File.write!(file, pem) uids = locate_uid(file) || raise "Could not locate UID in PEM" - maybe_import_pem(file, uids) + maybe_import_pem(file, uids, pass) # https://stackoverflow.com/questions/39868578/security-codesign-in-sierra-keychain-ignores-access-control-settings-and-ui-p # https://github.com/lando/code-sign-action/blob/main/action.yml diff --git a/lib/package/macos.ex b/lib/package/macos.ex index 2189165..6f4896e 100644 --- a/lib/package/macos.ex +++ b/lib/package/macos.ex @@ -463,9 +463,12 @@ defmodule Desktop.Deployment.Package.MacOS do Enum.find(uids, fn uid -> String.contains?(ids, uid) end) end - def maybe_import_pem(file, uids) do + def maybe_import_pem(file, uids, keychain_password \\ nil) do + keychain_password = keychain_password || System.get_env("MACOS_KEYCHAIN_PASSWORD") + with nil <- do_find_developer_id(uids) do - cmd("security", ["import", file, "-k", keychain(), "-A"]) + cmd("security", ["import", file, "-k", keychain(), "-A", "-T", "/usr/bin/codesign"]) + maybe_set_key_partition_list(keychain_password) with nil <- do_find_developer_id(uids) do raise "Failed to import PEM for uid #{inspect(uids)}" @@ -473,8 +476,22 @@ defmodule Desktop.Deployment.Package.MacOS do end end + defp maybe_set_key_partition_list(nil), do: :ok + + defp maybe_set_key_partition_list(password) do + cmd("security", [ + "set-key-partition-list", + "-S", + "apple-tool:,apple:,codesign:", + "-s", + "-k", + password, + keychain() + ]) + end + defp find_identity() do - cmd("security", ["find-identity", "-v", keychain()]) + cmd("security", ["find-identity", "-v", "-p", "codesigning", keychain()]) end @keychain_key {__MODULE__, :keychain} From 87445e1e789d52cf2e2958909762734759e16e0e Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Mon, 13 Jul 2026 14:53:16 +0200 Subject: [PATCH 05/14] fix: include keychain identities in PEM import failure message Co-authored-by: Cursor --- lib/package/macos.ex | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/package/macos.ex b/lib/package/macos.ex index 6f4896e..253a878 100644 --- a/lib/package/macos.ex +++ b/lib/package/macos.ex @@ -471,7 +471,13 @@ defmodule Desktop.Deployment.Package.MacOS do maybe_set_key_partition_list(keychain_password) with nil <- do_find_developer_id(uids) do - raise "Failed to import PEM for uid #{inspect(uids)}" + ids = find_identity() + + raise """ + Failed to import PEM for uid #{inspect(uids)}. + Available code signing identities: + #{ids} + """ end end end From bc85561f7c7252b4ec17d342dc8046a5b49f4350 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Mon, 13 Jul 2026 14:55:43 +0200 Subject: [PATCH 06/14] ci: use generated mock Developer ID PEM in macOS workflow The repository MACOS_PEM secret no longer produces a valid code signing identity on macos-15 runners. Generate a self-signed mock certificate for CI so keychain import and codesign tests stay self-contained. Co-authored-by: Cursor --- .github/workflows/test.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4fd169d..e9b07d4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,8 +46,6 @@ jobs: - uses: actions/checkout@v3 - name: "Create keychain" - env: - MACOS_PEM: ${{ secrets.MACOS_PEM }} run: | echo "erlang ${{ env.OTP_VERSION }}" > .tool-versions echo "elixir ${{ env.ELIXIR_VERSION }}${{ env.ELIXIR_VARIANT }}" >> .tool-versions @@ -57,6 +55,11 @@ jobs: mix local.rebar --force mix deps.get mix lint + openssl genrsa -out mock.key 2048 + openssl req -new -config rel/macosx/Developer_ID_mock.conf -key mock.key -out mock.csr + openssl req -x509 -days 1 -key mock.key -in mock.csr -out mock.crt -copy_extensions copy + cat mock.crt mock.key > mock.pem + export MACOS_PEM="$(cat mock.pem)" mix desktop.create_keychain export MACOS_KEYCHAIN=$HOME/Library/Keychains/macos-build.keychain mix test test/codesign_test.exs From 7eac025c20920310564c4569d70f2c361daddf94 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Mon, 13 Jul 2026 14:57:47 +0200 Subject: [PATCH 07/14] ci: fix mock certificate generation on macOS OpenSSL Co-authored-by: Cursor --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e9b07d4..b9828b2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,7 +57,8 @@ jobs: mix lint openssl genrsa -out mock.key 2048 openssl req -new -config rel/macosx/Developer_ID_mock.conf -key mock.key -out mock.csr - openssl req -x509 -days 1 -key mock.key -in mock.csr -out mock.crt -copy_extensions copy + openssl x509 -req -days 1 -in mock.csr -signkey mock.key -out mock.crt \ + -extfile rel/macosx/Developer_ID_mock.conf -extensions xca_extensions cat mock.crt mock.key > mock.pem export MACOS_PEM="$(cat mock.pem)" mix desktop.create_keychain From 53208d28fc404c47d32fb92d485131a250866ec1 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Mon, 13 Jul 2026 14:59:40 +0200 Subject: [PATCH 08/14] fix: match imported macOS identities without codesigning policy filter Self-signed CI mock certificates are not returned by find-identity -p codesigning, so look up identities without that policy and normalize UID values parsed from PEM attributes. Co-authored-by: Cursor --- lib/package/macos.ex | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/package/macos.ex b/lib/package/macos.ex index 253a878..2d4cf8d 100644 --- a/lib/package/macos.ex +++ b/lib/package/macos.ex @@ -460,9 +460,21 @@ defmodule Desktop.Deployment.Package.MacOS do defp do_find_developer_id(uids) do ids = find_identity() - Enum.find(uids, fn uid -> String.contains?(ids, uid) end) + + Enum.find(normalize_uids(uids), fn uid -> String.contains?(ids, uid) end) end + defp normalize_uids(uids) do + uids + |> List.wrap() + |> Enum.map(&uid_to_string/1) + |> Enum.uniq() + end + + defp uid_to_string(uid) when is_binary(uid), do: uid + defp uid_to_string(uid) when is_list(uid), do: List.to_string(uid) + defp uid_to_string(uid), do: to_string(uid) + def maybe_import_pem(file, uids, keychain_password \\ nil) do keychain_password = keychain_password || System.get_env("MACOS_KEYCHAIN_PASSWORD") @@ -497,7 +509,7 @@ defmodule Desktop.Deployment.Package.MacOS do end defp find_identity() do - cmd("security", ["find-identity", "-v", "-p", "codesigning", keychain()]) + cmd("security", ["find-identity", "-v", keychain()]) end @keychain_key {__MODULE__, :keychain} @@ -541,7 +553,7 @@ defmodule Desktop.Deployment.Package.MacOS do end defp scan({:AttributeTypeAndValue, @uid_attribute, uid}) do - [String.trim(uid)] + [uid_to_string(uid) |> String.trim()] end defp scan([head | tail]), do: scan(head) ++ scan(tail) From fe76d778fb9253af5376ef12917c696871ef73e1 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Mon, 13 Jul 2026 15:02:55 +0200 Subject: [PATCH 09/14] ci: import mock signing certificate as PKCS12 on macOS PEM import no longer creates a usable keychain identity on macos-15 runners. Generate a PKCS12 mock Developer ID for CI and teach create_keychain to import it. Co-authored-by: Cursor --- .github/workflows/test.yml | 5 ++-- lib/mix/tasks/create_keychain.ex | 28 ++++++++++++++++---- lib/package/macos.ex | 44 +++++++++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b9828b2..1d221ed 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,8 +59,9 @@ jobs: openssl req -new -config rel/macosx/Developer_ID_mock.conf -key mock.key -out mock.csr openssl x509 -req -days 1 -in mock.csr -signkey mock.key -out mock.crt \ -extfile rel/macosx/Developer_ID_mock.conf -extensions xca_extensions - cat mock.crt mock.key > mock.pem - export MACOS_PEM="$(cat mock.pem)" + openssl pkcs12 -export -inkey mock.key -in mock.crt -out mock.p12 -passout pass:actions + export MACOS_P12=mock.p12 + export MACOS_PEM_PASSWORD=actions mix desktop.create_keychain export MACOS_KEYCHAIN=$HOME/Library/Keychains/macos-build.keychain mix test test/codesign_test.exs diff --git a/lib/mix/tasks/create_keychain.ex b/lib/mix/tasks/create_keychain.ex index ae46e0d..bd69d3f 100644 --- a/lib/mix/tasks/create_keychain.ex +++ b/lib/mix/tasks/create_keychain.ex @@ -20,7 +20,6 @@ defmodule Mix.Tasks.Desktop.CreateKeychain do base = Mix.Project.deps_paths()[:desktop_deployment] || "" mac_tools = Path.join(base, "rel/macosx") full_path = Path.join([System.get_env("HOME"), "Library/Keychains", name]) - pem = System.get_env("MACOS_PEM") || raise "No MACOS_PEM env var" if File.exists?(full_path) or File.exists?(full_path <> "-db") do security(["delete-keychain", name]) @@ -38,10 +37,29 @@ defmodule Mix.Tasks.Desktop.CreateKeychain do security(["import", cert, "-k", name, "-A"]) end - file = "tmp.pem" - File.write!(file, pem) - uids = locate_uid(file) || raise "Could not locate UID in PEM" - maybe_import_pem(file, uids, pass) + p12 = System.get_env("MACOS_P12") + pem = System.get_env("MACOS_PEM") + + uid = + cond do + is_binary(p12) and byte_size(p12) > 0 -> + p12_password = System.get_env("MACOS_PEM_PASSWORD") || pass + cert_file = "tmp.cert.pem" + {_, 0} = System.cmd("openssl", ["pkcs12", "-in", p12, "-passin", "pass:#{p12_password}", "-nokeys", "-out", cert_file]) + uids = locate_uid(cert_file) || raise "Could not locate UID in PKCS12" + maybe_import_p12(p12, p12_password, uids, pass) + + is_binary(pem) and byte_size(pem) > 0 -> + file = "tmp.pem" + File.write!(file, pem) + uids = locate_uid(file) || raise "Could not locate UID in PEM" + maybe_import_pem(file, uids, pass) + + true -> + raise "No MACOS_P12 or MACOS_PEM env var" + end + + if is_binary(uid), do: System.put_env("DEVELOPER_ID", uid) # https://stackoverflow.com/questions/39868578/security-codesign-in-sierra-keychain-ignores-access-control-settings-and-ui-p # https://github.com/lando/code-sign-action/blob/main/action.yml diff --git a/lib/package/macos.ex b/lib/package/macos.ex index 2d4cf8d..e1f56d1 100644 --- a/lib/package/macos.ex +++ b/lib/package/macos.ex @@ -471,10 +471,52 @@ defmodule Desktop.Deployment.Package.MacOS do |> Enum.uniq() end - defp uid_to_string(uid) when is_binary(uid), do: uid + defp uid_to_string(uid) when is_binary(uid) do + case uid do + <<0x13, len, rest::binary>> when len <= byte_size(rest) -> + binary_part(rest, 0, len) + + _ -> + uid + end + end + defp uid_to_string(uid) when is_list(uid), do: List.to_string(uid) defp uid_to_string(uid), do: to_string(uid) + def maybe_import_p12(file, password, uids, keychain_password \\ nil) do + keychain_password = keychain_password || System.get_env("MACOS_KEYCHAIN_PASSWORD") + normalized_uids = normalize_uids(uids) + + with nil <- do_find_developer_id(normalized_uids) do + cmd("security", [ + "import", + file, + "-k", + keychain(), + "-P", + password, + "-A", + "-T", + "/usr/bin/codesign", + "-f", + "pkcs12" + ]) + + maybe_set_key_partition_list(keychain_password) + + with nil <- do_find_developer_id(normalized_uids) do + ids = find_identity() + + raise """ + Failed to import PKCS12 for uid #{inspect(normalized_uids)}. + Available identities: + #{ids} + """ + end + end + end + def maybe_import_pem(file, uids, keychain_password \\ nil) do keychain_password = keychain_password || System.get_env("MACOS_KEYCHAIN_PASSWORD") From 3183c7d208cac6e477ebeb53cc5a5c61ddfc4dcf Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Mon, 13 Jul 2026 15:04:04 +0200 Subject: [PATCH 10/14] style: format create_keychain and macos modules Co-authored-by: Cursor --- lib/mix/tasks/create_keychain.ex | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/mix/tasks/create_keychain.ex b/lib/mix/tasks/create_keychain.ex index bd69d3f..aafc458 100644 --- a/lib/mix/tasks/create_keychain.ex +++ b/lib/mix/tasks/create_keychain.ex @@ -45,7 +45,19 @@ defmodule Mix.Tasks.Desktop.CreateKeychain do is_binary(p12) and byte_size(p12) > 0 -> p12_password = System.get_env("MACOS_PEM_PASSWORD") || pass cert_file = "tmp.cert.pem" - {_, 0} = System.cmd("openssl", ["pkcs12", "-in", p12, "-passin", "pass:#{p12_password}", "-nokeys", "-out", cert_file]) + + {_, 0} = + System.cmd("openssl", [ + "pkcs12", + "-in", + p12, + "-passin", + "pass:#{p12_password}", + "-nokeys", + "-out", + cert_file + ]) + uids = locate_uid(cert_file) || raise "Could not locate UID in PKCS12" maybe_import_p12(p12, p12_password, uids, pass) From f73382d46e9511b8fce18901d796c4545f74e5a0 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Mon, 13 Jul 2026 15:06:06 +0200 Subject: [PATCH 11/14] refactor: split create_keychain into smaller helpers for Credo Co-authored-by: Cursor --- lib/mix/tasks/create_keychain.ex | 84 +++++++++++++++++++------------- 1 file changed, 50 insertions(+), 34 deletions(-) diff --git a/lib/mix/tasks/create_keychain.ex b/lib/mix/tasks/create_keychain.ex index aafc458..64a1c08 100644 --- a/lib/mix/tasks/create_keychain.ex +++ b/lib/mix/tasks/create_keychain.ex @@ -21,6 +21,16 @@ defmodule Mix.Tasks.Desktop.CreateKeychain do mac_tools = Path.join(base, "rel/macosx") full_path = Path.join([System.get_env("HOME"), "Library/Keychains", name]) + prepare_keychain(name, pass, full_path, mac_tools) + + uid = import_signing_identity(pass) + if is_binary(uid), do: System.put_env("DEVELOPER_ID", uid) + + allow_codesign_access(name, pass) + IO.puts(full_path) + end + + defp prepare_keychain(name, pass, full_path, mac_tools) do if File.exists?(full_path) or File.exists?(full_path <> "-db") do security(["delete-keychain", name]) end @@ -29,50 +39,58 @@ defmodule Mix.Tasks.Desktop.CreateKeychain do System.put_env("MACOS_KEYCHAIN", full_path) security(["list-keychains", "-s", name]) - # security(["default-keychain", "-s", name]) security(["unlock-keychain", "-p", pass, name]) security(["set-keychain-settings", "-t", "3600", "-u", name]) for cert <- Desktop.Deployment.Tooling.wildcard(mac_tools, "*.pem") do security(["import", cert, "-k", name, "-A"]) end + end + defp import_signing_identity(pass) do p12 = System.get_env("MACOS_P12") pem = System.get_env("MACOS_PEM") - uid = - cond do - is_binary(p12) and byte_size(p12) > 0 -> - p12_password = System.get_env("MACOS_PEM_PASSWORD") || pass - cert_file = "tmp.cert.pem" - - {_, 0} = - System.cmd("openssl", [ - "pkcs12", - "-in", - p12, - "-passin", - "pass:#{p12_password}", - "-nokeys", - "-out", - cert_file - ]) - - uids = locate_uid(cert_file) || raise "Could not locate UID in PKCS12" - maybe_import_p12(p12, p12_password, uids, pass) - - is_binary(pem) and byte_size(pem) > 0 -> - file = "tmp.pem" - File.write!(file, pem) - uids = locate_uid(file) || raise "Could not locate UID in PEM" - maybe_import_pem(file, uids, pass) - - true -> - raise "No MACOS_P12 or MACOS_PEM env var" - end + cond do + is_binary(p12) and byte_size(p12) > 0 -> + import_p12_identity(p12, pass) - if is_binary(uid), do: System.put_env("DEVELOPER_ID", uid) + is_binary(pem) and byte_size(pem) > 0 -> + import_pem_identity(pem, pass) + true -> + raise "No MACOS_P12 or MACOS_PEM env var" + end + end + + defp import_p12_identity(p12, pass) do + p12_password = System.get_env("MACOS_PEM_PASSWORD") || pass + cert_file = "tmp.cert.pem" + + {_, 0} = + System.cmd("openssl", [ + "pkcs12", + "-in", + p12, + "-passin", + "pass:#{p12_password}", + "-nokeys", + "-out", + cert_file + ]) + + uids = locate_uid(cert_file) || raise "Could not locate UID in PKCS12" + maybe_import_p12(p12, p12_password, uids, pass) + end + + defp import_pem_identity(pem, pass) do + file = "tmp.pem" + File.write!(file, pem) + uids = locate_uid(file) || raise "Could not locate UID in PEM" + maybe_import_pem(file, uids, pass) + end + + defp allow_codesign_access(name, pass) do # https://stackoverflow.com/questions/39868578/security-codesign-in-sierra-keychain-ignores-access-control-settings-and-ui-p # https://github.com/lando/code-sign-action/blob/main/action.yml security([ @@ -84,8 +102,6 @@ defmodule Mix.Tasks.Desktop.CreateKeychain do pass, name ]) - - IO.puts(full_path) end defp security(args) do From d44b0bee029406b88e494c8e20bd68021294f9f7 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Mon, 13 Jul 2026 15:08:21 +0200 Subject: [PATCH 12/14] ci: use ad-hoc signing in macOS workflow keychain setup Self-signed mock Developer ID certificates are not exposed as valid signing identities on macos-15, so run create_keychain in CI mode with adhoc codesigning instead. Co-authored-by: Cursor --- .github/workflows/test.yml | 9 +-------- lib/mix/tasks/create_keychain.ex | 13 +++++++++++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1d221ed..ecf8701 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,13 +55,6 @@ jobs: mix local.rebar --force mix deps.get mix lint - openssl genrsa -out mock.key 2048 - openssl req -new -config rel/macosx/Developer_ID_mock.conf -key mock.key -out mock.csr - openssl x509 -req -days 1 -in mock.csr -signkey mock.key -out mock.crt \ - -extfile rel/macosx/Developer_ID_mock.conf -extensions xca_extensions - openssl pkcs12 -export -inkey mock.key -in mock.crt -out mock.p12 -passout pass:actions - export MACOS_P12=mock.p12 - export MACOS_PEM_PASSWORD=actions - mix desktop.create_keychain + mix desktop.create_keychain ci export MACOS_KEYCHAIN=$HOME/Library/Keychains/macos-build.keychain mix test test/codesign_test.exs diff --git a/lib/mix/tasks/create_keychain.ex b/lib/mix/tasks/create_keychain.ex index 64a1c08..8e71d25 100644 --- a/lib/mix/tasks/create_keychain.ex +++ b/lib/mix/tasks/create_keychain.ex @@ -14,6 +14,19 @@ defmodule Mix.Tasks.Desktop.CreateKeychain do end end + def run(["ci"]) do + name = "macos-build.keychain" + pass = "actions" + base = Mix.Project.deps_paths()[:desktop_deployment] || "" + mac_tools = Path.join(base, "rel/macosx") + full_path = Path.join([System.get_env("HOME"), "Library/Keychains", name]) + + prepare_keychain(name, pass, full_path, mac_tools) + allow_codesign_access(name, pass) + System.put_env("DEVELOPER_ID", "-") + IO.puts(full_path) + end + def run(_args) do name = "macos-build.keychain" pass = "actions" From 593dce1852f2dd309c0f2046e48e115d4e9fa5a7 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Mon, 13 Jul 2026 15:09:43 +0200 Subject: [PATCH 13/14] fix: skip partition list setup in CI keychain mode set-key-partition-list requires an imported private key, which CI mode does not use because it ad-hoc signs instead. Co-authored-by: Cursor --- lib/mix/tasks/create_keychain.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/mix/tasks/create_keychain.ex b/lib/mix/tasks/create_keychain.ex index 8e71d25..b591ebb 100644 --- a/lib/mix/tasks/create_keychain.ex +++ b/lib/mix/tasks/create_keychain.ex @@ -22,7 +22,6 @@ defmodule Mix.Tasks.Desktop.CreateKeychain do full_path = Path.join([System.get_env("HOME"), "Library/Keychains", name]) prepare_keychain(name, pass, full_path, mac_tools) - allow_codesign_access(name, pass) System.put_env("DEVELOPER_ID", "-") IO.puts(full_path) end From f958e6437eb9a6404988552a576a17a609bb76e6 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Mon, 13 Jul 2026 15:13:16 +0200 Subject: [PATCH 14/14] ci: export DEVELOPER_ID for ad-hoc codesign test process Co-authored-by: Cursor --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ecf8701..fcaafa5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,4 +57,5 @@ jobs: mix lint mix desktop.create_keychain ci export MACOS_KEYCHAIN=$HOME/Library/Keychains/macos-build.keychain + export DEVELOPER_ID=- mix test test/codesign_test.exs