rust: packaging (Linux desktop entry, macOS bundle), cargo release script with update manifests, CI
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["**"]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test (${{ matrix.os }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-22.04, macos-14, windows-latest]
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: rust
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Linux dependencies (GUI libs, and sshd for the SSH integration tests)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxkbcommon-dev libgl1-mesa-dev libx11-dev libxcursor-dev libxrandr-dev libxi-dev libwayland-dev libgtk-3-dev openssh-server
|
||||
- run: cargo test --locked --workspace
|
||||
- run: cargo clippy --locked --workspace -- -D warnings
|
||||
@@ -17,118 +17,50 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: darwin/amd64
|
||||
runner: macos-13
|
||||
os: darwin
|
||||
arch: amd64
|
||||
archive_type: zip
|
||||
- platform: darwin/arm64
|
||||
runner: macos-14
|
||||
os: darwin
|
||||
arch: arm64
|
||||
archive_type: zip
|
||||
- platform: linux/amd64
|
||||
runner: ubuntu-latest
|
||||
os: linux
|
||||
arch: amd64
|
||||
archive_type: tar
|
||||
- platform: linux/arm64
|
||||
runner: ubuntu-24.04-arm64
|
||||
os: linux
|
||||
arch: arm64
|
||||
archive_type: tar
|
||||
- platform: windows/amd64
|
||||
runner: windows-latest
|
||||
os: windows
|
||||
arch: amd64
|
||||
archive_type: zip
|
||||
- platform: windows/arm64
|
||||
runner: ubuntu-latest
|
||||
os: windows
|
||||
arch: arm64
|
||||
archive_type: zip
|
||||
cross: true
|
||||
|
||||
- { platform: darwin/amd64, runner: macos-13 }
|
||||
- { platform: darwin/arm64, runner: macos-14 }
|
||||
- { platform: linux/amd64, runner: ubuntu-22.04 }
|
||||
- { platform: linux/arm64, runner: ubuntu-22.04-arm }
|
||||
- { platform: windows/amd64, runner: windows-latest }
|
||||
- { platform: windows/arm64, runner: windows-11-arm }
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: rust
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
- name: Rust toolchain (pinned by rust-toolchain.toml)
|
||||
run: rustup show active-toolchain || rustup toolchain install
|
||||
|
||||
- name: Install Wails
|
||||
run: go install github.com/wailsapp/wails/v2/cmd/wails@$(go list -m -f '{{.Version}}' github.com/wailsapp/wails/v2)
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: npm ci
|
||||
working-directory: frontend
|
||||
|
||||
- name: Install Linux build dependencies
|
||||
if: matrix.os == 'linux'
|
||||
- name: Linux build dependencies
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev
|
||||
sudo apt-get install -y libxkbcommon-dev libgl1-mesa-dev libx11-dev libxcursor-dev libxrandr-dev libxi-dev libwayland-dev libgtk-3-dev zip
|
||||
|
||||
- name: Install Windows packaging tools
|
||||
if: matrix.os == 'windows' && matrix.cross
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y zip
|
||||
- name: Test
|
||||
run: cargo test --locked --workspace
|
||||
|
||||
- name: Build (non-Windows runners)
|
||||
if: matrix.runner != 'windows-latest'
|
||||
- name: Build and package
|
||||
env:
|
||||
CGO_ENABLED: "0"
|
||||
run: |
|
||||
wails build -platform "${{ matrix.platform }}" -ldflags "-s -w"
|
||||
NEXUS_RELEASE_TARGETS: ${{ matrix.platform }}
|
||||
NEXUS_RELEASE_ROOT: ${{ github.workspace }}/build/releases
|
||||
run: ./release.sh
|
||||
|
||||
- name: Build (Windows runner)
|
||||
if: matrix.runner == 'windows-latest'
|
||||
shell: pwsh
|
||||
env:
|
||||
CGO_ENABLED: "0"
|
||||
run: |
|
||||
wails build -platform "${{ matrix.platform }}" -ldflags "-s -w"
|
||||
|
||||
- name: Package (zip)
|
||||
if: matrix.archive_type == 'zip' && matrix.runner != 'windows-latest'
|
||||
run: |
|
||||
archive="alchementary-link-${{ matrix.platform }}.zip"
|
||||
cd build/bin
|
||||
zip -r "../../${archive}" .
|
||||
|
||||
- name: Package (tar)
|
||||
if: matrix.archive_type == 'tar'
|
||||
run: |
|
||||
archive="alchementary-link-${{ matrix.platform }}.tar.gz"
|
||||
tar -czf "${archive}" -C build/bin .
|
||||
|
||||
- name: Package (Windows zip)
|
||||
if: matrix.archive_type == 'zip' && matrix.runner == 'windows-latest'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$archive = "alchementary-link-${{ matrix.platform }}.zip"
|
||||
Compress-Archive -Path "build/bin/*" -DestinationPath $archive
|
||||
|
||||
- name: Upload artifact
|
||||
- name: Upload artefacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: alchementary-link-${{ matrix.platform }}
|
||||
name: nexus-terminal-${{ matrix.runner }}
|
||||
path: |
|
||||
alchementary-link-${{ matrix.platform }}.zip
|
||||
alchementary-link-${{ matrix.platform }}.tar.gz
|
||||
build/releases/*/NexusTerminal-*.zip
|
||||
build/releases/manifest-*.json
|
||||
build/releases/version-*.txt
|
||||
|
||||
- name: Attach to GitHub Release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
alchementary-link-${{ matrix.platform }}.zip
|
||||
alchementary-link-${{ matrix.platform }}.tar.gz
|
||||
files: build/releases/*/NexusTerminal-*.zip
|
||||
|
||||
@@ -13,7 +13,11 @@ use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
/// The release version (`NEXUS_VERSION` from release.sh, e.g. "v1.2.0-abc1234"), else the crate version.
|
||||
pub const VERSION: &str = match option_env!("NEXUS_VERSION") {
|
||||
Some(v) => v,
|
||||
None => env!("CARGO_PKG_VERSION"),
|
||||
};
|
||||
const MAX_IMPORT_JSON_BYTES: usize = 5 * 1024 * 1024;
|
||||
|
||||
pub struct Service {
|
||||
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
# Install Nexus Terminal for the current user so it appears in the app launcher.
|
||||
# Usage: rust/packaging/linux/install.sh [path/to/nexus-terminal] (default: target/release/nexus-terminal)
|
||||
set -eu
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
BIN=${1:-"$HERE/../../target/release/nexus-terminal"}
|
||||
PREFIX=${XDG_DATA_HOME:-$HOME/.local/share}
|
||||
install -Dm755 "$BIN" "$HOME/.local/bin/nexus-terminal"
|
||||
install -Dm644 "$HERE/nexus-terminal.desktop" "$PREFIX/applications/nexus-terminal.desktop"
|
||||
install -Dm644 "$HERE/../../app/assets/icon/icon-256.png" "$PREFIX/icons/hicolor/256x256/apps/nexus-terminal.png"
|
||||
install -Dm644 "$HERE/../../app/assets/icon/icon-512.png" "$PREFIX/icons/hicolor/512x512/apps/nexus-terminal.png"
|
||||
update-desktop-database "$PREFIX/applications" 2>/dev/null || true
|
||||
gtk-update-icon-cache -f -t "$PREFIX/icons/hicolor" 2>/dev/null || true
|
||||
echo "installed: ~/.local/bin/nexus-terminal (make sure ~/.local/bin is on PATH)"
|
||||
@@ -0,0 +1,11 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Nexus Terminal
|
||||
GenericName=Terminal and Remote Connections
|
||||
Comment=SSH, Telnet, RAW TCP, Serial, VNC, IRC and GitHub sessions in one window
|
||||
Exec=nexus-terminal
|
||||
Icon=nexus-terminal
|
||||
Terminal=false
|
||||
Categories=System;TerminalEmulator;Network;RemoteAccess;
|
||||
Keywords=ssh;telnet;serial;sftp;vnc;irc;terminal;
|
||||
StartupWMClass=nexus-terminal
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>Nexus Terminal</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Nexus Terminal</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.andydixon.nexusterminal</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>NexusTerminal</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>NexusTerminal</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>__VERSION__</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>__VERSION__</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.developer-tools</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>11.0</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>© Andy Dixon</string>
|
||||
</dict>
|
||||
</plist>
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
#!/bin/sh
|
||||
# Build NexusTerminal.app, a macOS application bundle. Run on a Mac with Xcode command-line tools:
|
||||
#
|
||||
# rust/packaging/macos/build-app.sh # universal (Apple Silicon + Intel) if both
|
||||
# # targets are installed, else this Mac's arch
|
||||
# rust/packaging/macos/build-app.sh --dmg # also wrap it in a .dmg
|
||||
#
|
||||
# Output: rust/target/macos/NexusTerminal.app (and NexusTerminal-<version>.dmg).
|
||||
# Set NEXUS_BIN=/path/to/binary to bundle an existing build instead of compiling.
|
||||
set -eu
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
ROOT=$(cd "$HERE/../.." && pwd)
|
||||
cd "$ROOT"
|
||||
VERSION=${NEXUS_VERSION:-$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)}
|
||||
OUT="$ROOT/target/macos"
|
||||
APP="$OUT/NexusTerminal.app"
|
||||
|
||||
if [ -n "${NEXUS_BIN:-}" ]; then
|
||||
BIN="$NEXUS_BIN"
|
||||
else
|
||||
targets=""
|
||||
for t in aarch64-apple-darwin x86_64-apple-darwin; do
|
||||
if rustup target list --installed 2>/dev/null | grep -qx "$t"; then targets="$targets $t"; fi
|
||||
done
|
||||
[ -n "$targets" ] || targets=" $(rustc -vV | sed -n 's/^host: //p')"
|
||||
bins=""
|
||||
for t in $targets; do
|
||||
echo "building $t"
|
||||
cargo build --release -p nexus --target "$t"
|
||||
bins="$bins target/$t/release/nexus-terminal"
|
||||
done
|
||||
mkdir -p "$OUT"
|
||||
set -- $bins
|
||||
if [ $# -gt 1 ]; then
|
||||
lipo -create "$@" -output "$OUT/nexus-terminal-universal"
|
||||
BIN="$OUT/nexus-terminal-universal"
|
||||
else
|
||||
BIN="$1"
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -rf "$APP"
|
||||
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"
|
||||
cp "$BIN" "$APP/Contents/MacOS/NexusTerminal"
|
||||
chmod +x "$APP/Contents/MacOS/NexusTerminal"
|
||||
cp "$HERE/NexusTerminal.icns" "$APP/Contents/Resources/NexusTerminal.icns"
|
||||
sed "s/__VERSION__/$VERSION/g" "$HERE/Info.plist" > "$APP/Contents/Info.plist"
|
||||
printf 'APPL????' > "$APP/Contents/PkgInfo"
|
||||
|
||||
if command -v plutil >/dev/null; then plutil -lint "$APP/Contents/Info.plist"; fi
|
||||
# Ad-hoc sign so Apple Silicon runs it. For distribution, sign with a Developer ID and notarize:
|
||||
# codesign --force --options runtime --sign "Developer ID Application: …" NexusTerminal.app
|
||||
# xcrun notarytool submit … --wait && xcrun stapler staple NexusTerminal.app
|
||||
if command -v codesign >/dev/null; then codesign --force --deep --sign - "$APP"; fi
|
||||
|
||||
if [ "${1:-}" = "--dmg" ] && command -v hdiutil >/dev/null; then
|
||||
DMG="$OUT/NexusTerminal-$VERSION.dmg"
|
||||
rm -f "$DMG"
|
||||
hdiutil create -volname "Nexus Terminal" -srcfolder "$APP" -ov -format UDZO "$DMG"
|
||||
echo "built $DMG"
|
||||
fi
|
||||
echo "built $APP"
|
||||
Executable
+109
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env bash
|
||||
# release.sh — build and package Nexus Terminal release artefacts with cargo.
|
||||
#
|
||||
# Produces, under build/releases/<version>/ (repo root):
|
||||
# NexusTerminal-<os>-<arch>[.exe|.app] and NexusTerminal-<os>-<arch>.zip
|
||||
# and next to them manifest-<os>-<arch>.json + version-<os>-<arch>.txt for the update server
|
||||
# (updates/index.php), in the same format the Wails build produced.
|
||||
#
|
||||
# Targets default to this machine's native one; set NEXUS_RELEASE_TARGETS to a comma list such as
|
||||
# "linux/amd64,windows/amd64" (windows/* from Linux needs the mingw-w64 toolchain and rustup target).
|
||||
# CI (.github/workflows/release-builds.yml) builds every platform natively.
|
||||
set -euo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT="$(cd "$HERE/.." && pwd)"
|
||||
cd "$HERE"
|
||||
|
||||
DEFAULT_UPDATE_URL="https://alchementary.co.uk/download/link/manifest-{platform}-{arch}.json"
|
||||
DEFAULT_RELEASE_BASE_URL="https://alchementary.co.uk/download/link"
|
||||
|
||||
log() { echo -e "\033[0;32m[nexus]\033[0m $1"; }
|
||||
warn() { echo -e "\033[1;33m[nexus]\033[0m $1"; }
|
||||
|
||||
host_target() {
|
||||
local os arch
|
||||
case "$(uname -s)" in Darwin) os=darwin ;; Linux) os=linux ;; MINGW*|MSYS*|CYGWIN*) os=windows ;; *) os=unknown ;; esac
|
||||
case "$(uname -m)" in x86_64|amd64) arch=amd64 ;; arm64|aarch64) arch=arm64 ;; *) arch=unknown ;; esac
|
||||
echo "$os/$arch"
|
||||
}
|
||||
|
||||
rust_triple() {
|
||||
case "$1" in
|
||||
linux/amd64) echo x86_64-unknown-linux-gnu ;;
|
||||
linux/arm64) echo aarch64-unknown-linux-gnu ;;
|
||||
darwin/amd64) echo x86_64-apple-darwin ;;
|
||||
darwin/arm64) echo aarch64-apple-darwin ;;
|
||||
windows/amd64) if [ "$(uname -s)" = Linux ]; then echo x86_64-pc-windows-gnu; else echo x86_64-pc-windows-msvc; fi ;;
|
||||
windows/arm64) echo aarch64-pc-windows-msvc ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
VERSION="${NEXUS_VERSION:-$(git describe --tags --abbrev=0 2>/dev/null || echo dev)-$(git rev-parse --short HEAD 2>/dev/null || echo unknown)}"
|
||||
export NEXUS_VERSION="$VERSION"
|
||||
export NEXUS_UPDATE_URL="${NEXUS_UPDATE_URL:-$DEFAULT_UPDATE_URL}"
|
||||
BASE_URL="${NEXUS_RELEASE_BASE_URL:-$DEFAULT_RELEASE_BASE_URL}"
|
||||
RELEASE_ROOT="${NEXUS_RELEASE_ROOT:-$ROOT/build/releases}"
|
||||
OUT="$RELEASE_ROOT/$VERSION"
|
||||
mkdir -p "$OUT"
|
||||
: > "$OUT/.release-files"
|
||||
|
||||
IFS=',' read -r -a TARGETS <<< "${NEXUS_RELEASE_TARGETS:-$(host_target)}"
|
||||
errors=0
|
||||
for target in "${TARGETS[@]}"; do
|
||||
os="${target%%/*}"; arch="${target##*/}"
|
||||
triple="$(rust_triple "$target")" || { warn "unknown target $target"; errors=$((errors+1)); continue; }
|
||||
base="NexusTerminal-${os}-${arch}"
|
||||
log "Building $target ($triple), version $VERSION"
|
||||
rustup target add "$triple" >/dev/null 2>&1 || true
|
||||
if ! cargo build --release --locked -p nexus --target "$triple"; then
|
||||
warn "build failed for $target"; errors=$((errors+1)); continue
|
||||
fi
|
||||
bin="target/$triple/release/nexus-terminal"
|
||||
case "$os" in
|
||||
darwin)
|
||||
NEXUS_BIN="$bin" packaging/macos/build-app.sh
|
||||
rm -rf "$OUT/$base.app"
|
||||
cp -R target/macos/NexusTerminal.app "$OUT/$base.app"
|
||||
(cd "$OUT" && rm -f "$base.zip" && zip -qry "$base.zip" "$base.app")
|
||||
;;
|
||||
windows)
|
||||
cp "$bin.exe" "$OUT/$base.exe"
|
||||
(cd "$OUT" && rm -f "$base.zip" && zip -qj "$base.zip" "$base.exe")
|
||||
;;
|
||||
*)
|
||||
cp "$bin" "$OUT/$base"
|
||||
strip "$OUT/$base" 2>/dev/null || true
|
||||
stage="$(mktemp -d)"
|
||||
cp "$OUT/$base" "$stage/nexus-terminal"
|
||||
cp packaging/linux/nexus-terminal.desktop packaging/linux/install.sh "$stage/"
|
||||
mkdir -p "$stage/app/assets/icon" && cp app/assets/icon/icon-256.png app/assets/icon/icon-512.png "$stage/app/assets/icon/"
|
||||
sed -i 's|\$HERE/../../app|$HERE/app|; s|\$HERE/../../target/release/nexus-terminal|$HERE/nexus-terminal|' "$stage/install.sh"
|
||||
(cd "$stage" && rm -f "$OUT/$base.zip" && zip -qr "$OUT/$base.zip" .)
|
||||
rm -rf "$stage"
|
||||
;;
|
||||
esac
|
||||
echo "$os|$arch|$base.zip" >> "$OUT/.release-files"
|
||||
done
|
||||
|
||||
published_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
while IFS='|' read -r os arch filename; do
|
||||
[ -z "$os" ] && continue
|
||||
sha="$(shasum -a 256 "$OUT/$filename" 2>/dev/null || sha256sum "$OUT/$filename")"; sha="${sha%% *}"
|
||||
size="$(wc -c < "$OUT/$filename" | tr -d ' ')"
|
||||
echo "$VERSION" > "$RELEASE_ROOT/version-${os}-${arch}.txt"
|
||||
cat > "$RELEASE_ROOT/manifest-${os}-${arch}.json" <<JSON
|
||||
{
|
||||
"latestVersion": "${VERSION}",
|
||||
"publishedAt": "${published_at}",
|
||||
"releaseURL": "${BASE_URL}/",
|
||||
"files": [
|
||||
{"platform":"${os}","arch":"${arch}","filename":"${filename}","url":"${BASE_URL}/${filename}","sha256":"${sha}","size":${size}}
|
||||
]
|
||||
}
|
||||
JSON
|
||||
done < "$OUT/.release-files"
|
||||
rm -f "$OUT/.release-files"
|
||||
log "Artefacts in $OUT"
|
||||
exit $errors
|
||||
Reference in New Issue
Block a user