Files
Termix/.github/workflows/electron.yml
T
Luke Gustafson 52f4e51ae0 v2.3.2 (#874)
* fix: patch critical security vulnerabilities (GHSA-5fqh, GHSA-ccm8, GHSA-wqfw, GHSA-xmjh)

- Remove passwordHash from /users/list API response
- Require both password and TOTP code for MFA-critical operations
- Restrict tunnel kill commands to tunnelMarker-only matching
- Add session ownership middleware for file manager endpoints

* fix: allow navigating away from split-view to non-pane tabs

Show the normal view container on top of the split view when the active
tab is not assigned to any pane, so users can switch to dashboard or
other tabs while split mode is active.

Closes #739

* fix: add inline quick-action buttons on host name row

Show Terminal, Files, RDP, and VNC shortcut icons on the host name row
on hover, so users can launch connections with a single click without
expanding the full action tray.

Closes #736

* fix: restore SSH keepalive interval to 30s to prevent random disconnects

Revert keepalive defaults from 60s/5 to 30s/3 across terminal, tunnel,
and server-stats SSH connections. The 60s interval introduced in 2.3.0
causes firewalls and NAT devices to drop idle connections before the
next keepalive probe.

Closes #733

* fix: apply guacamole-lite protocol patch in Docker builds

The Dockerfile uses --ignore-scripts which skips the postinstall hook
that patches guacamole-lite for guacd 1.6.0 protocol VERSION_1_5_0.
Without this patch, the timezone handshake instruction is not sent for
protocol versions above 1.1.0, causing VNC connections to fail
immediately on connect.

Closes #734

* fix: show correct icons for network interface types

Detect interface type from name pattern and show appropriate icons:
WiFi for wlan/wl*, Ethernet (Cable) for eth/en*, Container for
docker/bridge/virtual, generic Network for others.

Closes #720

* fix: resolve sudo password for shared host users

The password endpoint required hosts.userId to match the requesting
user, which fails for shared hosts. Now falls back to decrypting with
the owner's key when the requesting user doesn't own the host.

Closes #717

* fix: use jump hosts for online status check and metrics collection

Status polling now pings the first jump host instead of the unreachable
target when jump hosts are configured. The /metrics/start endpoint now
tunnels through the jump host chain to reach the target host.

Closes #716

* fix: broaden sudo prompt detection for newer distros

Add patterns for 'password for <user>:' and bare 'Password:' prompts
in addition to the existing [sudo] and sudo: patterns. Covers Ubuntu
26.04 and other distros that use different sudo prompt formats.

Closes #718

* fix: recalculate terminal layout after web fonts load

xterm.js measures character widths at open() time. If custom fonts
haven't loaded yet, measurements use the fallback font and spacing
becomes incorrect. Now refresh and re-fit the terminal once
document.fonts.ready resolves.

Closes #710

* fix: improve terminal cwd detection and initial directory command

Remove '&& pwd' from initial directory command — the shell prompt
shows the new directory naturally. Fixes PowerShell 5.1 which doesn't
support '&&' as a statement separator.

Prepend Ctrl+U to get_cwd command to clear any pending input before
injecting the cwd probe, reducing interference with foreground programs.

Closes #713, #714

* fix: decode base64 file content as UTF-8 in file manager

Replace bare atob() with TextDecoder('utf-8') for base64 content
decoding. atob() only handles Latin-1, so multi-byte UTF-8 characters
like 'é' were decoded as 'é'.

Closes #719

* fix: normalize lazy import default exports for iOS compatibility

Wrap all lazy() imports with explicit .then(m => ({ default: m.default }))
to ensure consistent module resolution across platforms. iOS Safari/WebView
may handle bare lazy(() => import(...)) differently, returning the module
object instead of extracting the default export.

Closes #721

* fix: prevent RDP display from snapping back after container resize

Remove immediate rescaleDisplay() from ResizeObserver callback. The
display.onresize event already triggers rescaling when the RDP server
responds with the new resolution. Calling rescaleDisplay before the
server responds uses stale display dimensions, causing the bottom of
the screen to be truncated.

Closes #725

* fix: add portal Desktop DBus permission for Flatpak URL opening

Flatpak sandbox blocks window.open() without the portal permission,
causing terminal link clicks to open about:blank. Add talk-name for
org.freedesktop.portal.Desktop to enable xdg-desktop-portal URL
handling.

Closes #704

* chore: remove unused code and fix PR checks (#851)

* chore: remove unused frontend code

* chore: prune unused theme exports

* ci: fix pr check failures

* chore: reduce lint warnings

* feat(oidc): expose admin_group via OIDC_ADMIN_GROUP env var (#828)

The admin-group OIDC sync added in 2.3.0 (#782) reads `config.admin_group`
to sync the user's admin flag from OIDC group membership on each login.
That field is only populated when the OIDC config is stored in the
in-app DB — `getOIDCConfigFromEnv()` does not expose it, so deployments
using the env-var config path (declarative IaC: Helm/Compose/Puppet)
cannot enable the feature without abandoning env vars and pasting the
client_secret into the admin UI.

Add `admin_group: process.env.OIDC_ADMIN_GROUP || ""` to the env-config
return type and object. Backward compatible: when unset, the existing
`if (config.admin_group)` guard at users.ts:1336 keeps the sync block
skipped, matching today's behavior.

* chore: reduce explicit-any warnings

* chore: reduce more explicit-any warnings

* chore: reduce lint warnings

* chore: silence intentional hook dependency warnings

* chore: clean dependency tooling

* chore: narrow frontend tsconfig scope

* chore: reduce type assertion debt

* refactor: split host manager components

* refactor: split host editor sections

* refactor: split api client modules

* refactor: split more api clients

* refactor: split user settings api clients

* refactor: split tab and history api clients

* refactor: split tunnel api clients

* refactor: split server stats api client

* refactor: split file manager data api

* refactor: split ssh file operations api

* refactor: split host editor general tab

* refactor: split host editor guacamole tabs

* refactor: split ssh host management api

* refactor: split admin general settings sections

* refactor: split admin database section

* refactor: split admin management sections

* refactor: split admin keys and dialogs

* refactor: split system status api clients

* refactor: split user route helpers

* refactor: split host route helpers

* refactor: split file manager ssh helpers

* refactor: split file manager session helpers

* refactor: split file manager listing routes

* refactor: split host opkssh routes

* refactor: split file manager content routes

* refactor: split user api key routes

* refactor: split host folder routes

* refactor: split user settings routes

* refactor: split user totp routes

* refactor: split host file manager bookmark routes

* refactor: split file manager operation routes

* refactor: split server stats settings routes

* refactor: split user session routes

* refactor: split host command history routes

* refactor: split server stats viewer routes

* refactor: split docker container routes

* refactor: split user oidc account routes

* refactor: split host autostart routes

* refactor: split host internal routes

* refactor: split host network routes

* refactor: split user password reset routes

* refactor: split user admin routes

* refactor: split user data access routes

* refactor: split credential key routes

* refactor: split credential deploy routes

* refactor: split host bulk routes

* refactor: split server stats connection helpers

* refactor: split tunnel helpers

* refactor: split file manager action routes

* refactor: split terminal auth helpers

* refactor: split terminal jump host helpers

* refactor: split tunnel relay helpers

* refactor: split tunnel socks relay helpers

* refactor: split tunnel c2s relay handlers

* refactor: split server stats session helpers

* refactor: split terminal presentation helpers

* refactor: split file manager presentation helpers

* refactor: split file manager toolbar

* fix(guacamole-lite): send name instruction for protocol >= 1.3.0

The Guacamole protocol added the `name` handshake instruction in 1.3.0
(an optional human-readable identifier for the joining user). guacd 1.6.0
began requiring it during the VNC handshake even when negotiating older
protocol versions, causing connections to silently drop right after the
"User joined" log line with no client-visible error.

This patch extends scripts/patch-guacamole-lite.cjs with a third
idempotent string-replacement that injects the `name` instruction send
when guacamole-lite has negotiated protocol VERSION_1_3_0 or VERSION_1_5_0.

Verified end-to-end: guacd debug logs now show `Processing instruction:
name` and `Client is using protocol version "VERSION_1_5_0"` (previously
stuck at VERSION_1_1_0). VNC session connects successfully against
guacd 1.5.5 / macOS Tahoe target.

Related: Termix-SSH/Support#567, #734

* fix: resolve recent support bugs

* fix(admin): wire up OIDC-to-password link dialog submit + visibility

The admin user-management UI already shipped a link icon and a "Link
Account" dialog, but two things blocked the flow:

1. The submit button had no onClick handler and the username input was
   uncontrolled (no value/onChange). Clicking "Link Accounts" was a
   no-op — no network request, no console error, no toast.
2. The link icon's visibility condition was `user.isOidc &&
   !user.passwordHash`, which hid the button on OIDC users that had
   been auto-provisioned with a passwordHash. Termix's OIDC provisioning
   sets a passwordHash by default, so the button was hidden on virtually
   every OIDC-provisioned user.

This change:
- Adds `linkOIDCToPasswordAccount` to the imports from `@/main-axios`.
- Adds two pieces of dialog state: `linkAccountTargetUsername` and
  `linkAccountSubmitting`.
- Makes the dialog's Input field a controlled component.
- Wires the submit Button's onClick to call `linkOIDCToPasswordAccount`,
  emit success/error toasts, refresh the local user list, and close
  the dialog.
- Loosens the visibility condition to `user.isOidc` (the backend
  handler already enforces all integrity checks).
- Adds `linkAccountSuccess`, `linkAccountFailed`, and
  `linkAccountInProgress` translation keys to `en.json`.

Verified locally: full Docker build via docker/Dockerfile passes;
`tsc --noEmit` is clean; `prettier --check .` is clean; ESLint produces
the same warning count as upstream (16 pre-existing `any`-type warnings,
0 errors).

* fix: support native oidc callbacks (#856)

* docs: add cloudflare tunnel guidance (#857)

* fix: sync appearance preferences (#858)

* fix: pass through terminal tab completion (#859)

* fix: resolve terminal jump hosts server-side (#860)

* fix(electron): auto-allow SSL certificates for private network hosts (#861)

Add private network IP detection (RFC 1918, link-local, loopback, IPv6
ULA) to the Electron certificate-error handler so that connections to
local/private servers like 192.168.x.x bypass SSL validation
automatically. Also add an explicit "Allow invalid certificate" toggle
in the server config UI for public HTTPS servers with self-signed certs.

* fix: restore host password copy actions (#862)

* feat: support single-host direct tunnels (ssh -L style) (#863)

Add direct tunnel mode that uses a single SSH host for port forwarding,
matching the behavior of ssh -L / ssh -R / ssh -D without requiring a
second endpoint host in the Termix database. The Termix server creates a
local TCP listener and forwards through the SSH channel directly.

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* fix: backend build errors (Type)

* fix: mobile auth failing to login with webview

* fix: mobile app geting incorrectly sent auth token

* feat: commit existing frontend/backend e2e/unit tests (skipped tests containing private info like OIDC and real server testing)

* feat: host-to-host file transfer via server relay

* feat: removed host management from command palette, fixed command palette opening wrong protocol, export/import failing for ssh key hosts, docker ssh2 native crypto not compiled, persisted terminal tabs attempt SSh on RDP hosts after migration, improved layout for click to expand hosts, show ip/username without having to hover over hosts

* fix: credentials not indexing into host manager until refresh

* feat: update credentials lists to match hosts list UI/UX

* feat: add rename folder UI

* feat: improve transfer to host UI/UX

* chore: increment ver

* feat: improve transfer to host UI

* feat: implement initial auto release system

---------

Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
Co-authored-by: nicodarge <43711429+nicodarge@users.noreply.github.com>
Co-authored-by: Raman Gupta <7243222+raman325@users.noreply.github.com>
Co-authored-by: luc <luc_cook@hotmail.co.uk>
2026-06-04 14:16:53 -05:00

992 lines
39 KiB
YAML

name: Build and Push Electron App
on:
workflow_dispatch:
inputs:
build_type:
description: "Platform to build for"
required: true
default: "all"
type: choice
options:
- all
- windows
- linux
- macos
artifact_destination:
description: "What to do with the built app"
required: true
default: "file"
type: choice
options:
- none
- file
- release
- submit
workflow_call:
inputs:
build_type:
description: "Platform to build for (all, windows, linux, macos)"
required: true
type: string
artifact_destination:
description: "What to do with the built app (none, file, release, submit)"
required: true
type: string
release_tag:
description: "Explicit release tag to upload assets to (defaults to latest release when empty)"
required: false
type: string
default: ""
outputs:
macos_universal_dmg_sha256:
description: "SHA256 of the universal macOS DMG (for Homebrew cask)"
value: ${{ jobs.build-macos.outputs.dmg_sha256 }}
jobs:
build-windows:
runs-on: blacksmith-4vcpu-windows-2025
if: (inputs.build_type == 'all' || inputs.build_type == 'windows' || inputs.build_type == '') && inputs.artifact_destination != 'submit'
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Get version
id: package-version
run: |
$VERSION = (Get-Content package.json | ConvertFrom-Json).version
echo "version=$VERSION" >> $env:GITHUB_OUTPUT
- name: Build Windows (All Architectures)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npm run build && npx electron-builder --win --x64 --ia32
- name: Upload Windows x64 NSIS Installer
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_windows_x64_nsis.exe') != '' && inputs.artifact_destination != 'none'
with:
name: termix_windows_x64_nsis
path: release/termix_windows_x64_nsis.exe
retention-days: 30
- name: Upload Windows ia32 NSIS Installer
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_windows_ia32_nsis.exe') != '' && inputs.artifact_destination != 'none'
with:
name: termix_windows_ia32_nsis
path: release/termix_windows_ia32_nsis.exe
retention-days: 30
- name: Upload Windows x64 MSI Installer
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_windows_x64_msi.msi') != '' && inputs.artifact_destination != 'none'
with:
name: termix_windows_x64_msi
path: release/termix_windows_x64_msi.msi
retention-days: 30
- name: Upload Windows ia32 MSI Installer
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_windows_ia32_msi.msi') != '' && inputs.artifact_destination != 'none'
with:
name: termix_windows_ia32_msi
path: release/termix_windows_ia32_msi.msi
retention-days: 30
- name: Create Windows x64 Portable zip
if: hashFiles('release/win-unpacked/*') != ''
run: |
Compress-Archive -Path "release\win-unpacked\*" -DestinationPath "termix_windows_x64_portable.zip"
- name: Create Windows ia32 Portable zip
if: hashFiles('release/win-ia32-unpacked/*') != ''
run: |
Compress-Archive -Path "release\win-ia32-unpacked\*" -DestinationPath "termix_windows_ia32_portable.zip"
- name: Upload Windows x64 Portable
uses: actions/upload-artifact@v4
if: hashFiles('termix_windows_x64_portable.zip') != '' && inputs.artifact_destination != 'none'
with:
name: termix_windows_x64_portable
path: termix_windows_x64_portable.zip
retention-days: 30
- name: Upload Windows ia32 Portable
uses: actions/upload-artifact@v4
if: hashFiles('termix_windows_ia32_portable.zip') != '' && inputs.artifact_destination != 'none'
with:
name: termix_windows_ia32_portable
path: termix_windows_ia32_portable.zip
retention-days: 30
build-linux:
runs-on: blacksmith-8vcpu-ubuntu-2404
if: (inputs.build_type == 'all' || inputs.build_type == 'linux' || inputs.build_type == '') && inputs.artifact_destination != 'submit'
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "npm"
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libfuse2 flatpak flatpak-builder imagemagick
- name: Install dependencies
run: |
npm ci
npm install --force @rollup/rollup-linux-x64-gnu
npm install --force @rollup/rollup-linux-arm64-gnu
npm install --force @rollup/rollup-linux-arm-gnueabihf
- name: Build Linux x64
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DEBUG: electron-builder
run: npm run build && npx electron-builder --linux --x64
- name: Build Linux arm64 and armv7l
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npx electron-builder --linux --arm64 --armv7l
- name: Rename Linux artifacts for consistency
run: |
cd release
if [ -f "termix_linux_amd64_deb.deb" ]; then
mv "termix_linux_amd64_deb.deb" "termix_linux_x64_deb.deb"
fi
if [ -f "termix_linux_x86_64_appimage.AppImage" ]; then
mv "termix_linux_x86_64_appimage.AppImage" "termix_linux_x64_appimage.AppImage"
fi
cd ..
- name: Upload Linux x64 AppImage
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_x64_appimage.AppImage') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_x64_appimage
path: release/termix_linux_x64_appimage.AppImage
retention-days: 30
- name: Upload Linux arm64 AppImage
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_arm64_appimage.AppImage') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_arm64_appimage
path: release/termix_linux_arm64_appimage.AppImage
retention-days: 30
- name: Upload Linux armv7l AppImage
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_armv7l_appimage.AppImage') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_armv7l_appimage
path: release/termix_linux_armv7l_appimage.AppImage
retention-days: 30
- name: Upload Linux x64 DEB
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_x64_deb.deb') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_x64_deb
path: release/termix_linux_x64_deb.deb
retention-days: 30
- name: Upload Linux arm64 DEB
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_arm64_deb.deb') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_arm64_deb
path: release/termix_linux_arm64_deb.deb
retention-days: 30
- name: Upload Linux armv7l DEB
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_armv7l_deb.deb') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_armv7l_deb
path: release/termix_linux_armv7l_deb.deb
retention-days: 30
- name: Upload Linux x64 tar.gz
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_x64_portable.tar.gz') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_x64_portable
path: release/termix_linux_x64_portable.tar.gz
retention-days: 30
- name: Upload Linux arm64 tar.gz
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_arm64_portable.tar.gz') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_arm64_portable
path: release/termix_linux_arm64_portable.tar.gz
retention-days: 30
- name: Upload Linux armv7l tar.gz
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_armv7l_portable.tar.gz') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_armv7l_portable
path: release/termix_linux_armv7l_portable.tar.gz
retention-days: 30
- name: Add Flathub repository
run: |
sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
- name: Install Flatpak runtime and SDK
run: |
sudo flatpak install -y flathub org.freedesktop.Platform//24.08
sudo flatpak install -y flathub org.freedesktop.Sdk//24.08
sudo flatpak install -y flathub org.electronjs.Electron2.BaseApp//24.08
- name: Get version for Flatpak
id: flatpak-version
run: |
VERSION=$(node -p "require('./package.json').version")
RELEASE_DATE=$(date +%Y-%m-%d)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "release_date=$RELEASE_DATE" >> $GITHUB_OUTPUT
- name: Prepare Flatpak files
run: |
VERSION="${{ steps.flatpak-version.outputs.version }}"
RELEASE_DATE="${{ steps.flatpak-version.outputs.release_date }}"
CHECKSUM_X64=$(sha256sum "release/termix_linux_x64_appimage.AppImage" | awk '{print $1}')
CHECKSUM_ARM64=$(sha256sum "release/termix_linux_arm64_appimage.AppImage" | awk '{print $1}')
mkdir -p flatpak-build
cp flatpak/com.karmaa.termix.yml flatpak-build/
cp flatpak/com.karmaa.termix.desktop flatpak-build/
cp flatpak/com.karmaa.termix.metainfo.xml flatpak-build/
cp public/icon.svg flatpak-build/com.karmaa.termix.svg
convert public/icon.png -resize 256x256 flatpak-build/icon-256.png
convert public/icon.png -resize 128x128 flatpak-build/icon-128.png
cd flatpak-build
sed -i "s|https://github.com/Termix-SSH/Termix/releases/download/release-VERSION_PLACEHOLDER-tag/termix_linux_x64_appimage.AppImage|file://$(realpath ../release/termix_linux_x64_appimage.AppImage)|g" com.karmaa.termix.yml
sed -i "s|https://github.com/Termix-SSH/Termix/releases/download/release-VERSION_PLACEHOLDER-tag/termix_linux_arm64_appimage.AppImage|file://$(realpath ../release/termix_linux_arm64_appimage.AppImage)|g" com.karmaa.termix.yml
sed -i "s/CHECKSUM_X64_PLACEHOLDER/$CHECKSUM_X64/g" com.karmaa.termix.yml
sed -i "s/CHECKSUM_ARM64_PLACEHOLDER/$CHECKSUM_ARM64/g" com.karmaa.termix.yml
sed -i "s/VERSION_PLACEHOLDER/$VERSION/g" com.karmaa.termix.metainfo.xml
sed -i "s/DATE_PLACEHOLDER/$RELEASE_DATE/g" com.karmaa.termix.metainfo.xml
- name: Build Flatpak bundle
run: |
cd flatpak-build
flatpak-builder --repo=repo --force-clean --disable-rofiles-fuse build-dir com.karmaa.termix.yml
ARCH=$(uname -m)
if [ "$ARCH" = "x86_64" ]; then
FLATPAK_ARCH="x86_64"
elif [ "$ARCH" = "aarch64" ]; then
FLATPAK_ARCH="aarch64"
else
FLATPAK_ARCH="$ARCH"
fi
flatpak build-bundle repo ../release/termix_linux_flatpak.flatpak com.karmaa.termix --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo
- name: Create flatpakref file
run: |
VERSION="${{ steps.flatpak-version.outputs.version }}"
cp flatpak/com.karmaa.termix.flatpakref release/
sed -i "s|VERSION_PLACEHOLDER|release-${VERSION}-tag|g" release/com.karmaa.termix.flatpakref
- name: Upload Flatpak bundle
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_flatpak.flatpak') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_flatpak
path: release/termix_linux_flatpak.flatpak
retention-days: 30
- name: Upload Flatpakref
uses: actions/upload-artifact@v4
if: hashFiles('release/com.karmaa.termix.flatpakref') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_flatpakref
path: release/com.karmaa.termix.flatpakref
retention-days: 30
build-macos:
runs-on: blacksmith-6vcpu-macos-latest
if: (inputs.build_type == 'macos' || inputs.build_type == 'all') && inputs.artifact_destination != 'submit'
needs: []
permissions:
contents: write
outputs:
dmg_sha256: ${{ steps.dmg-checksum.outputs.sha256 }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "npm"
- name: Install dependencies
run: |
npm ci
npm install --force @rollup/rollup-darwin-arm64
npm install dmg-license
- name: Check for Code Signing Certificates
id: check_certs
run: |
if [ -n "${{ secrets.MAC_BUILD_CERTIFICATE_BASE64 }}" ] && [ -n "${{ secrets.MAC_P12_PASSWORD }}" ]; then
echo "has_certs=true" >> $GITHUB_OUTPUT
fi
- name: Import Code Signing Certificates
if: steps.check_certs.outputs.has_certs == 'true'
env:
MAC_BUILD_CERTIFICATE_BASE64: ${{ secrets.MAC_BUILD_CERTIFICATE_BASE64 }}
MAC_INSTALLER_CERTIFICATE_BASE64: ${{ secrets.MAC_INSTALLER_CERTIFICATE_BASE64 }}
MAC_P12_PASSWORD: ${{ secrets.MAC_P12_PASSWORD }}
MAC_KEYCHAIN_PASSWORD: ${{ secrets.MAC_KEYCHAIN_PASSWORD }}
run: |
APP_CERT_PATH=$RUNNER_TEMP/app_certificate.p12
INSTALLER_CERT_PATH=$RUNNER_TEMP/installer_certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
echo -n "$MAC_BUILD_CERTIFICATE_BASE64" | base64 --decode -o $APP_CERT_PATH
if [ -n "$MAC_INSTALLER_CERTIFICATE_BASE64" ]; then
echo -n "$MAC_INSTALLER_CERTIFICATE_BASE64" | base64 --decode -o $INSTALLER_CERT_PATH
fi
security create-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security import $APP_CERT_PATH -P "$MAC_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
if [ -f "$INSTALLER_CERT_PATH" ]; then
security import $INSTALLER_CERT_PATH -P "$MAC_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
fi
security list-keychain -d user -s $KEYCHAIN_PATH
security find-identity -v -p codesigning $KEYCHAIN_PATH
- name: Build macOS App Store Package
if: steps.check_certs.outputs.has_certs == 'true'
env:
ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES: true
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_OPTIONS: --max-old-space-size=4096
run: |
CURRENT_VERSION=$(node -p "require('./package.json').version")
BUILD_VERSION="${{ github.run_number }}"
npm run build && npx electron-builder --mac mas --universal --config.buildVersion="$BUILD_VERSION"
- name: Clean up MAS keychain before DMG build
if: steps.check_certs.outputs.has_certs == 'true'
run: |
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true
- name: Check for Developer ID Certificates
id: check_dev_id_certs
run: |
if [ -n "${{ secrets.DEVELOPER_ID_CERTIFICATE_BASE64 }}" ] && [ -n "${{ secrets.DEVELOPER_ID_P12_PASSWORD }}" ]; then
echo "has_dev_id_certs=true" >> $GITHUB_OUTPUT
fi
- name: Import Developer ID Certificates
if: steps.check_dev_id_certs.outputs.has_dev_id_certs == 'true'
env:
DEVELOPER_ID_CERTIFICATE_BASE64: ${{ secrets.DEVELOPER_ID_CERTIFICATE_BASE64 }}
DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64: ${{ secrets.DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64 }}
DEVELOPER_ID_P12_PASSWORD: ${{ secrets.DEVELOPER_ID_P12_PASSWORD }}
MAC_KEYCHAIN_PASSWORD: ${{ secrets.MAC_KEYCHAIN_PASSWORD }}
run: |
DEV_CERT_PATH=$RUNNER_TEMP/dev_certificate.p12
DEV_INSTALLER_CERT_PATH=$RUNNER_TEMP/dev_installer_certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/dev-signing.keychain-db
echo -n "$DEVELOPER_ID_CERTIFICATE_BASE64" | base64 --decode -o $DEV_CERT_PATH
if [ -n "$DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64" ]; then
echo -n "$DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64" | base64 --decode -o $DEV_INSTALLER_CERT_PATH
fi
security create-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security import $DEV_CERT_PATH -P "$DEVELOPER_ID_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
if [ -f "$DEV_INSTALLER_CERT_PATH" ]; then
security import $DEV_INSTALLER_CERT_PATH -P "$DEVELOPER_ID_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
fi
security list-keychain -d user -s $KEYCHAIN_PATH
security find-identity -v -p codesigning $KEYCHAIN_PATH
- name: Build macOS DMG
env:
ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES: true
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
NODE_OPTIONS: --max-old-space-size=4096
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ "${{ steps.check_certs.outputs.has_certs }}" != "true" ]; then
npm run build
fi
npx electron-builder --mac dmg --universal --x64 --arm64 --publish never
- name: Upload macOS MAS PKG
if: steps.check_certs.outputs.has_certs == 'true' && hashFiles('release/termix_macos_universal_mas.pkg') != '' && (inputs.artifact_destination == 'file' || inputs.artifact_destination == 'release' || inputs.artifact_destination == 'submit')
uses: actions/upload-artifact@v4
with:
name: termix_macos_universal_mas
path: release/termix_macos_universal_mas.pkg
retention-days: 30
if-no-files-found: warn
- name: Upload macOS Universal DMG
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_macos_universal_dmg.dmg') != '' && inputs.artifact_destination != 'none'
with:
name: termix_macos_universal_dmg
path: release/termix_macos_universal_dmg.dmg
retention-days: 30
- name: Upload macOS x64 DMG
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_macos_x64_dmg.dmg') != '' && inputs.artifact_destination != 'none'
with:
name: termix_macos_x64_dmg
path: release/termix_macos_x64_dmg.dmg
retention-days: 30
- name: Upload macOS arm64 DMG
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_macos_arm64_dmg.dmg') != '' && inputs.artifact_destination != 'none'
with:
name: termix_macos_arm64_dmg
path: release/termix_macos_arm64_dmg.dmg
retention-days: 30
- name: Get version for Homebrew
id: homebrew-version
run: |
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Compute universal DMG checksum
id: dmg-checksum
if: hashFiles('release/termix_macos_universal_dmg.dmg') != ''
run: |
CHECKSUM=$(shasum -a 256 "release/termix_macos_universal_dmg.dmg" | awk '{print $1}')
echo "sha256=$CHECKSUM" >> $GITHUB_OUTPUT
- name: Generate Homebrew Cask
if: hashFiles('release/termix_macos_universal_dmg.dmg') != '' && (inputs.artifact_destination == 'file' || inputs.artifact_destination == 'release')
run: |
VERSION="${{ steps.homebrew-version.outputs.version }}"
DMG_PATH="release/termix_macos_universal_dmg.dmg"
CHECKSUM=$(shasum -a 256 "$DMG_PATH" | awk '{print $1}')
mkdir -p homebrew-generated
cp Casks/termix.rb homebrew-generated/termix.rb
sed -i '' "s/VERSION_PLACEHOLDER/$VERSION/g" homebrew-generated/termix.rb
sed -i '' "s/CHECKSUM_PLACEHOLDER/$CHECKSUM/g" homebrew-generated/termix.rb
sed -i '' "s|version \".*\"|version \"$VERSION\"|g" homebrew-generated/termix.rb
sed -i '' "s|sha256 \".*\"|sha256 \"$CHECKSUM\"|g" homebrew-generated/termix.rb
sed -i '' "s|release-[0-9.]*-tag|release-$VERSION-tag|g" homebrew-generated/termix.rb
- name: Upload Homebrew Cask as artifact
uses: actions/upload-artifact@v4
if: hashFiles('homebrew-generated/termix.rb') != '' && inputs.artifact_destination == 'file'
with:
name: termix_macos_homebrew_cask
path: homebrew-generated/termix.rb
retention-days: 30
- name: Upload Homebrew Cask to release
if: hashFiles('homebrew-generated/termix.rb') != '' && inputs.artifact_destination == 'release'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ steps.homebrew-version.outputs.version }}"
RELEASE_TAG="release-$VERSION-tag"
gh release list --repo ${{ github.repository }} --limit 100 | grep -q "$RELEASE_TAG" || {
echo "Release $RELEASE_TAG not found"
exit 1
}
gh release upload "$RELEASE_TAG" homebrew-generated/termix.rb --repo ${{ github.repository }} --clobber
- name: Clean up keychains
if: always()
run: |
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true
security delete-keychain $RUNNER_TEMP/dev-signing.keychain-db || true
submit-to-chocolatey:
runs-on: blacksmith-4vcpu-windows-2025
if: inputs.artifact_destination == 'submit' && (inputs.build_type == 'all' || inputs.build_type == 'windows' || inputs.build_type == '')
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Get version from package.json
id: package-version
run: |
$VERSION = (Get-Content package.json | ConvertFrom-Json).version
echo "version=$VERSION" >> $env:GITHUB_OUTPUT
- name: Download and prepare MSI info from public release
id: msi-info
run: |
$VERSION = "${{ steps.package-version.outputs.version }}"
$MSI_NAME = "termix_windows_x64_msi.msi"
$DOWNLOAD_URL = "https://github.com/Termix-SSH/Termix/releases/download/release-$($VERSION)-tag/$($MSI_NAME)"
Write-Host "Downloading from $DOWNLOAD_URL"
New-Item -ItemType Directory -Force -Path "release_asset"
$DOWNLOAD_PATH = "release_asset\$MSI_NAME"
try {
Invoke-WebRequest -Uri $DOWNLOAD_URL -OutFile $DOWNLOAD_PATH -UseBasicParsing
} catch {
Write-Error "Failed to download MSI from $DOWNLOAD_URL. Please ensure the release and asset exist."
exit 1
}
$CHECKSUM = (Get-FileHash -Path $DOWNLOAD_PATH -Algorithm SHA256).Hash
echo "msi_name=$MSI_NAME" >> $env:GITHUB_OUTPUT
echo "checksum=$CHECKSUM" >> $env:GITHUB_OUTPUT
- name: Prepare Chocolatey package
run: |
$VERSION = "${{ steps.package-version.outputs.version }}"
$CHECKSUM = "${{ steps.msi-info.outputs.checksum }}"
$MSI_NAME = "${{ steps.msi-info.outputs.msi_name }}"
$DOWNLOAD_URL = "https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$MSI_NAME"
New-Item -ItemType Directory -Force -Path "choco-build"
Copy-Item -Path "chocolatey\*" -Destination "choco-build" -Recurse -Force
$installScript = Get-Content "choco-build\tools\chocolateyinstall.ps1" -Raw -Encoding UTF8
$installScript = $installScript -replace 'DOWNLOAD_URL_PLACEHOLDER', $DOWNLOAD_URL
$installScript = $installScript -replace 'CHECKSUM_PLACEHOLDER', $CHECKSUM
[System.IO.File]::WriteAllText("$PWD\choco-build\tools\chocolateyinstall.ps1", $installScript, [System.Text.UTF8Encoding]::new($false))
$nuspec = Get-Content "choco-build\termix-ssh.nuspec" -Raw -Encoding UTF8
$nuspec = $nuspec -replace 'VERSION_PLACEHOLDER', $VERSION
[System.IO.File]::WriteAllText("$PWD\choco-build\termix-ssh.nuspec", $nuspec, [System.Text.UTF8Encoding]::new($false))
- name: Install Chocolatey
run: |
Set-ExecutionPolicy Bypass -Scope Process -Force
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
- name: Pack Chocolatey package
run: |
cd choco-build
choco pack termix-ssh.nuspec
if ($LASTEXITCODE -ne 0) {
throw "Chocolatey push failed with exit code $LASTEXITCODE"
}
- name: Check for Chocolatey API Key
id: check_choco_key
run: |
if ("${{ secrets.CHOCOLATEY_API_KEY }}" -ne "") {
echo "has_key=true" >> $env:GITHUB_OUTPUT
}
- name: Push to Chocolatey
if: steps.check_choco_key.outputs.has_key == 'true'
run: |
$VERSION = "${{ steps.package-version.outputs.version }}"
cd choco-build
choco apikey --key "${{ secrets.CHOCOLATEY_API_KEY }}" --source https://push.chocolatey.org/
try {
choco push "termix-ssh.$VERSION.nupkg" --source https://push.chocolatey.org/
if ($LASTEXITCODE -eq 0) {
} else {
throw "Chocolatey push failed with exit code $LASTEXITCODE"
}
} catch {
}
- name: Upload Chocolatey package as artifact
uses: actions/upload-artifact@v4
with:
name: chocolatey-package
path: choco-build/*.nupkg
retention-days: 30
submit-to-flatpak:
runs-on: blacksmith-8vcpu-ubuntu-2404
if: inputs.artifact_destination == 'submit' && (inputs.build_type == 'all' || inputs.build_type == 'linux' || inputs.build_type == '')
needs: []
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Get version from package.json
id: package-version
run: |
VERSION=$(node -p "require('./package.json').version")
RELEASE_DATE=$(date +%Y-%m-%d)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "release_date=$RELEASE_DATE" >> $GITHUB_OUTPUT
- name: Download and prepare AppImage info from public release
id: appimage-info
run: |
VERSION="${{ steps.package-version.outputs.version }}"
mkdir -p release_assets
APPIMAGE_X64_NAME="termix_linux_x64_appimage.AppImage"
URL_X64="https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$APPIMAGE_X64_NAME"
PATH_X64="release_assets/$APPIMAGE_X64_NAME"
echo "Downloading x64 AppImage from $URL_X64"
curl -L -o "$PATH_X64" "$URL_X64"
chmod +x "$PATH_X64"
CHECKSUM_X64=$(sha256sum "$PATH_X64" | awk '{print $1}')
APPIMAGE_ARM64_NAME="termix_linux_arm64_appimage.AppImage"
URL_ARM64="https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$APPIMAGE_ARM64_NAME"
PATH_ARM64="release_assets/$APPIMAGE_ARM64_NAME"
echo "Downloading arm64 AppImage from $URL_ARM64"
curl -L -o "$PATH_ARM64" "$URL_ARM64"
chmod +x "$PATH_ARM64"
CHECKSUM_ARM64=$(sha256sum "$PATH_ARM64" | awk '{print $1}')
echo "appimage_x64_name=$APPIMAGE_X64_NAME" >> $GITHUB_OUTPUT
echo "checksum_x64=$CHECKSUM_X64" >> $GITHUB_OUTPUT
echo "appimage_arm64_name=$APPIMAGE_ARM64_NAME" >> $GITHUB_OUTPUT
echo "checksum_arm64=$CHECKSUM_ARM64" >> $GITHUB_OUTPUT
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y imagemagick
- name: Prepare Flatpak submission files
run: |
VERSION="${{ steps.package-version.outputs.version }}"
CHECKSUM_X64="${{ steps.appimage-info.outputs.checksum_x64 }}"
CHECKSUM_ARM64="${{ steps.appimage-info.outputs.checksum_arm64 }}"
RELEASE_DATE="${{ steps.package-version.outputs.release_date }}"
APPIMAGE_X64_NAME="${{ steps.appimage-info.outputs.appimage_x64_name }}"
APPIMAGE_ARM64_NAME="${{ steps.appimage-info.outputs.appimage_arm64_name }}"
mkdir -p flatpak-submission
cp flatpak/com.karmaa.termix.yml flatpak-submission/
cp flatpak/com.karmaa.termix.desktop flatpak-submission/
cp flatpak/com.karmaa.termix.metainfo.xml flatpak-submission/
cp flatpak/flathub.json flatpak-submission/
cp public/icon.svg flatpak-submission/com.karmaa.termix.svg
convert public/icon.png -resize 256x256 flatpak-submission/icon-256.png
convert public/icon.png -resize 128x128 flatpak-submission/icon-128.png
sed -i "s/VERSION_PLACEHOLDER/$VERSION/g" flatpak-submission/com.karmaa.termix.yml
sed -i "s/CHECKSUM_X64_PLACEHOLDER/$CHECKSUM_X64/g" flatpak-submission/com.karmaa.termix.yml
sed -i "s/CHECKSUM_ARM64_PLACEHOLDER/$CHECKSUM_ARM64/g" flatpak-submission/com.karmaa.termix.yml
sed -i "s/VERSION_PLACEHOLDER/$VERSION/g" flatpak-submission/com.karmaa.termix.metainfo.xml
sed -i "s/DATE_PLACEHOLDER/$RELEASE_DATE/g" flatpak-submission/com.karmaa.termix.metainfo.xml
- name: Upload Flatpak submission as artifact
uses: actions/upload-artifact@v4
with:
name: flatpak-submission
path: flatpak-submission/*
retention-days: 30
submit-to-homebrew:
runs-on: blacksmith-6vcpu-macos-latest
if: inputs.artifact_destination == 'submit' && (inputs.build_type == 'all' || inputs.build_type == 'macos')
needs: []
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Get version from package.json
id: package-version
run: |
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Download and prepare DMG info from public release
id: dmg-info
run: |
VERSION="${{ steps.package-version.outputs.version }}"
DMG_NAME="termix_macos_universal_dmg.dmg"
URL="https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$DMG_NAME"
mkdir -p release_asset
DOWNLOAD_PATH="release_asset/$DMG_NAME"
echo "Downloading DMG from $URL"
if command -v curl &> /dev/null; then
curl -L -o "$DOWNLOAD_PATH" "$URL"
elif command -v wget &> /dev/null; then
wget -O "$DOWNLOAD_PATH" "$URL"
else
echo "Neither curl nor wget is available, installing curl"
brew install curl
curl -L -o "$DOWNLOAD_PATH" "$URL"
fi
CHECKSUM=$(shasum -a 256 "$DOWNLOAD_PATH" | awk '{print $1}')
echo "dmg_name=$DMG_NAME" >> $GITHUB_OUTPUT
echo "checksum=$CHECKSUM" >> $GITHUB_OUTPUT
- name: Prepare Homebrew submission files
run: |
VERSION="${{ steps.package-version.outputs.version }}"
CHECKSUM="${{ steps.dmg-info.outputs.checksum }}"
DMG_NAME="${{ steps.dmg-info.outputs.dmg_name }}"
mkdir -p homebrew-submission/Casks/t
cp Casks/termix.rb homebrew-submission/Casks/t/termix.rb
sed -i '' "s/VERSION_PLACEHOLDER/$VERSION/g" homebrew-submission/Casks/t/termix.rb
sed -i '' "s/CHECKSUM_PLACEHOLDER/$CHECKSUM/g" homebrew-submission/Casks/t/termix.rb
- name: Verify Cask syntax
run: |
ruby -c homebrew-submission/Casks/t/termix.rb
- name: Upload Homebrew submission as artifact
uses: actions/upload-artifact@v4
with:
name: homebrew-submission
path: homebrew-submission/*
retention-days: 30
upload-to-release:
runs-on: blacksmith-8vcpu-ubuntu-2404
if: inputs.artifact_destination == 'release'
needs: [build-windows, build-linux, build-macos]
permissions:
contents: write
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Resolve release tag
id: get_release
env:
GH_TOKEN: ${{ github.token }}
INPUT_TAG: ${{ inputs.release_tag }}
run: |
if [ -n "$INPUT_TAG" ]; then
echo "RELEASE_TAG=$INPUT_TAG" >> $GITHUB_ENV
else
echo "RELEASE_TAG=$(gh release list --repo ${{ github.repository }} --limit 1 --json tagName -q '.[0].tagName')" >> $GITHUB_ENV
fi
- name: Upload artifacts to latest release
run: |
cd artifacts
for dir in */; do
cd "$dir"
for file in *;
do
if [ -f "$file" ]; then
gh release upload "$RELEASE_TAG" "$file" --repo ${{ github.repository }} --clobber
fi
done
cd ..
done
env:
GH_TOKEN: ${{ github.token }}
submit-to-testflight:
runs-on: blacksmith-6vcpu-macos-latest
if: inputs.artifact_destination == 'submit' && (inputs.build_type == 'all' || inputs.build_type == 'macos')
needs: []
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "npm"
- name: Install dependencies
run: |
npm ci
npm install --force @rollup/rollup-darwin-arm64
npm install dmg-license
- name: Check for Code Signing Certificates
id: check_certs
run: |
if [ -n "${{ secrets.MAC_BUILD_CERTIFICATE_BASE64 }}" ] && [ -n "${{ secrets.MAC_P12_PASSWORD }}" ]; then
echo "has_certs=true" >> $GITHUB_OUTPUT
fi
- name: Import Code Signing Certificates
if: steps.check_certs.outputs.has_certs == 'true'
env:
MAC_BUILD_CERTIFICATE_BASE64: ${{ secrets.MAC_BUILD_CERTIFICATE_BASE64 }}
MAC_INSTALLER_CERTIFICATE_BASE64: ${{ secrets.MAC_INSTALLER_CERTIFICATE_BASE64 }}
MAC_P12_PASSWORD: ${{ secrets.MAC_P12_PASSWORD }}
MAC_KEYCHAIN_PASSWORD: ${{ secrets.MAC_KEYCHAIN_PASSWORD }}
run: |
APP_CERT_PATH=$RUNNER_TEMP/app_certificate.p12
INSTALLER_CERT_PATH=$RUNNER_TEMP/installer_certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
echo -n "$MAC_BUILD_CERTIFICATE_BASE64" | base64 --decode -o $APP_CERT_PATH
if [ -n "$MAC_INSTALLER_CERTIFICATE_BASE64" ]; then
echo -n "$MAC_INSTALLER_CERTIFICATE_BASE64" | base64 --decode -o $INSTALLER_CERT_PATH
fi
security create-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security import $APP_CERT_PATH -P "$MAC_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
if [ -f "$INSTALLER_CERT_PATH" ]; then
security import $INSTALLER_CERT_PATH -P "$MAC_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
fi
security list-keychain -d user -s $KEYCHAIN_PATH
security find-identity -v -p codesigning $KEYCHAIN_PATH
- name: Build macOS App Store Package
if: steps.check_certs.outputs.has_certs == 'true'
env:
ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES: true
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_OPTIONS: --max-old-space-size=4096
run: |
CURRENT_VERSION=$(node -p "require('./package.json').version")
BUILD_VERSION="${{ github.run_number }}"
npm run build && npx electron-builder --mac mas --universal --config.buildVersion="$BUILD_VERSION"
- name: Check for App Store Connect API credentials
id: check_asc_creds
run: |
if [ -n "${{ secrets.APPLE_KEY_ID }}" ] && [ -n "${{ secrets.APPLE_ISSUER_ID }}" ] && [ -n "${{ secrets.APPLE_KEY_CONTENT }}" ]; then
echo "has_credentials=true" >> $GITHUB_OUTPUT
fi
- name: Setup Ruby for Fastlane
if: steps.check_asc_creds.outputs.has_credentials == 'true'
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.2"
bundler-cache: false
- name: Install Fastlane
if: steps.check_asc_creds.outputs.has_credentials == 'true'
run: |
gem install fastlane -N
- name: Deploy to App Store Connect (TestFlight)
if: steps.check_asc_creds.outputs.has_credentials == 'true'
run: |
PKG_FILE=$(find release -name "termix_macos_universal_mas.pkg" -type f | head -n 1)
if [ -z "$PKG_FILE" ]; then
echo "PKG file not found, exiting."
exit 1
fi
mkdir -p ~/private_keys
echo "${{ secrets.APPLE_KEY_CONTENT }}" | base64 --decode > ~/private_keys/AuthKey_${{ secrets.APPLE_KEY_ID }}.p8
xcrun altool --upload-app -f "$PKG_FILE" \
--type macos \
--apiKey "${{ secrets.APPLE_KEY_ID }}" \
--apiIssuer "${{ secrets.APPLE_ISSUER_ID }}"
continue-on-error: true
- name: Clean up keychains
if: always()
run: |
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true