NetDesk 1.5.0 — source release
This commit is contained in:
commit
40318aafdd
227 files changed
+80798
No files matched your search
@@ -0,0 +1,130 @@
|
||||
# NetDesk — Windows Packaging
|
||||
|
||||
Build scripts that turn the Rust `netdesk-viewer` binary into the two Windows
|
||||
distributions **NetDesk by Akronlogic** ships:
|
||||
|
||||
| Edition | Artifact | Install? |
|
||||
|---------------|---------------------------------|----------|
|
||||
| **Portable** | `NetDesk-Portable-win64.zip` | No — unzip and run |
|
||||
| **Installer** | `NetDesk-Setup-win64.exe` | Yes — Start Menu + Desktop shortcuts, uninstaller |
|
||||
|
||||
Everything in this folder is self-contained; nothing here touches the Rust
|
||||
sources. All outputs land in **`packaging\dist\`**.
|
||||
|
||||
```
|
||||
packaging/
|
||||
├── build-portable.ps1 builds + stages the portable folder, zips it
|
||||
├── build-installer.ps1 stages, runs makensis, signs both artifacts
|
||||
├── netdesk.nsi NSIS (Modern UI 2) installer definition
|
||||
├── README.md this file
|
||||
└── dist/ (generated)
|
||||
├── NetDesk-Portable/ staging folder (also fed to the installer)
|
||||
│ ├── NetDesk.exe = target/release/netdesk-viewer.exe, renamed
|
||||
│ ├── libstdc++-6.dll MinGW C++ runtime (required at runtime)
|
||||
│ ├── libgcc_s_seh-1.dll MinGW GCC runtime
|
||||
│ ├── libwinpthread-1.dll MinGW threading runtime
|
||||
│ └── README.txt
|
||||
├── NetDesk-Portable-win64.zip ← portable artifact
|
||||
└── NetDesk-Setup-win64.exe ← installer artifact (needs makensis)
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Rust (windows-GNU toolchain)** + the MinGW-w64 toolchain at
|
||||
`C:\mingw-netdesk\mingw64\bin`. The scripts prepend this and `~\.cargo\bin`
|
||||
to `PATH` before calling cargo, matching the project's manual build step:
|
||||
|
||||
```sh
|
||||
export PATH="/c/mingw-netdesk/mingw64/bin:$HOME/.cargo/bin:$PATH"
|
||||
```
|
||||
|
||||
- **NSIS** (for the installer only). If `makensis` is missing,
|
||||
`build-installer.ps1` still produces the staging folder and prints the
|
||||
install command:
|
||||
|
||||
```powershell
|
||||
winget install --id NSIS.NSIS -e
|
||||
# or download: https://nsis.sourceforge.io/Download
|
||||
```
|
||||
|
||||
## Produce the PORTABLE zip
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File packaging\build-portable.ps1
|
||||
```
|
||||
|
||||
Builds `cargo build --release -p netdesk-viewer`, assembles
|
||||
`dist\NetDesk-Portable\` (NetDesk.exe + the 3 DLLs + README.txt) and writes
|
||||
`dist\NetDesk-Portable-win64.zip`. Add `-SkipBuild` to reuse an existing
|
||||
`target\release\netdesk-viewer.exe`.
|
||||
|
||||
## Produce the INSTALLER exe
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File packaging\build-installer.ps1
|
||||
```
|
||||
|
||||
Runs the portable staging, then compiles `netdesk.nsi` with `makensis` into
|
||||
`dist\NetDesk-Setup-win64.exe`, and signs both `NetDesk.exe` and the setup exe.
|
||||
Useful flags: `-SkipBuild`, `-NoSign`, `-Thumbprint <THUMB>`.
|
||||
|
||||
Compile the installer by hand (after staging) with:
|
||||
|
||||
```powershell
|
||||
makensis /DSTAGEDIR=packaging\dist\NetDesk-Portable /DOUTFILE=packaging\dist\NetDesk-Setup-win64.exe packaging\netdesk.nsi
|
||||
```
|
||||
|
||||
The installer installs into `%ProgramFiles%\NetDesk`, creates Start Menu and
|
||||
Desktop shortcuts, registers an uninstaller in Add/Remove Programs, and offers
|
||||
an **optional, off-by-default** per-user autostart (HKCU `...\Run`).
|
||||
|
||||
## Code signing
|
||||
|
||||
Signing is handled by the project helper `..\certs\sign-release.ps1`
|
||||
(Akronlogic cert, thumbprint `YOUR_CODE_SIGNING_CERT_THUMBPRINT`,
|
||||
Sectigo timestamp). `build-installer.ps1`:
|
||||
|
||||
1. invokes `sign-release.ps1` against the release tree, then
|
||||
2. signs the staged `NetDesk.exe` (a renamed copy) and the finished
|
||||
`NetDesk-Setup-win64.exe` with the **same** certificate + timestamp
|
||||
mechanism (that helper is hardcoded to the `target\release` paths, so the
|
||||
staged/setup files are signed with the identical cert here).
|
||||
|
||||
> **Public trust note:** the bundled Akronlogic certificate is self-issued, so
|
||||
> signatures are only trusted on machines that ran `certs\trust-akronlogic.cmd`.
|
||||
> For signatures trusted on **every** Windows PC with no action by the user, a
|
||||
> **CA-issued code-signing certificate** (Sectigo/DigiCert, chaining to a root
|
||||
> in the Microsoft Trusted Root Program) is required — pass it to the helper
|
||||
> with `-Thumbprint <cert-in-store>` or `-PfxPath cert.pfx -PfxPassword ...`.
|
||||
> Self-signed certificates never qualify for public trust.
|
||||
|
||||
Skip signing entirely with `-NoSign` (e.g. for a local test build).
|
||||
|
||||
## Notes
|
||||
|
||||
- The portable and installer editions ship the **same** `NetDesk.exe` plus the
|
||||
same three MinGW runtime DLLs. The viewer imports `libstdc++-6.dll` at
|
||||
runtime, so the DLLs must always sit beside the exe.
|
||||
- Deleting the portable folder fully removes it. The installer edition is
|
||||
removed from Add/Remove Programs (or `uninstall.exe`).
|
||||
|
||||
## iOS (macOS only)
|
||||
|
||||
`build-ios.sh` (+ the `ios/` CocoaPods templates) builds the signed iOS app on a
|
||||
Mac: it adds the Rust iOS targets, compiles `netdesk-core` for device +
|
||||
simulator, assembles `NetDeskCore.xcframework`, wires it into the Flutter
|
||||
`ui/ios/` project, and runs `flutter build ipa`. It cannot run on Windows (Xcode
|
||||
is required). One command on the Mac:
|
||||
|
||||
```bash
|
||||
packaging/build-ios.sh --team <APPLE_TEAM_ID>
|
||||
```
|
||||
|
||||
Full walkthrough — Apple Developer Program, signing, the VideoToolbox decode
|
||||
path, every command — is in [`../docs/build-ios.md`](../docs/build-ios.md).
|
||||
|
||||
```
|
||||
packaging/ios/
|
||||
├── netdesk_core.podspec vendors NetDeskCore.xcframework + the VideoToolbox frameworks
|
||||
└── Podfile Flutter iOS Podfile template with the netdesk_core pod
|
||||
```
|
||||
@@ -0,0 +1,402 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
NetDesk - Android build pipeline.
|
||||
|
||||
Compiles the Rust FFI layer (netdesk-frb, a cdylib that statically links
|
||||
netdesk-core) into per-ABI .so libraries with cargo-ndk, drops them into
|
||||
the Flutter jniLibs tree, optionally provisions a release signing keystore,
|
||||
then produces the release APK, the per-ABI split APKs and the Play Store
|
||||
App Bundle (.aab).
|
||||
|
||||
Developed by Akronlogic (https://akronlogic.com).
|
||||
|
||||
.DESCRIPTION
|
||||
The Flutter UI (ui/) binds, via flutter_rust_bridge, to the compiled Rust
|
||||
library shipped as a shared object inside the APK/AAB under
|
||||
lib/<abi>/libnetdesk_frb.so (the Dart loader looks for stem 'netdesk_frb').
|
||||
This script is the single entry point that wires the Android toolchain:
|
||||
|
||||
netdesk-frb --cargo-ndk--> ui/android/app/src/main/jniLibs/<abi>/libnetdesk_frb.so
|
||||
Flutter --gradle-----> release .apk / split .apk / .aab (signed)
|
||||
|
||||
THE ONE COMMAND (from the repo root):
|
||||
|
||||
powershell -ExecutionPolicy Bypass -File packaging\build-android.ps1
|
||||
|
||||
First release-signed run (keystore does not exist yet):
|
||||
|
||||
$env:NETDESK_KEYSTORE_PASSWORD = '<your-strong-password>'
|
||||
powershell -ExecutionPolicy Bypass -File packaging\build-android.ps1 -GenerateKeystore
|
||||
|
||||
.NOTES
|
||||
Prerequisites (the script verifies / auto-provisions what it can):
|
||||
* Rust toolchain on PATH (cargo, rustup).
|
||||
* cargo-ndk (auto-installed with `cargo install cargo-ndk` if absent).
|
||||
* rustup Android std (auto-added if absent).
|
||||
* Android NDK (default C:\dev\android-ndk; override with -NdkHome
|
||||
or $env:ANDROID_NDK_HOME). REQUIRED for the Rust step.
|
||||
* Android SDK (default %LOCALAPPDATA%\Android\Sdk).
|
||||
* Flutter (default C:\dev\flutter\bin\flutter.bat).
|
||||
* A JDK for keytool / Gradle (Android Studio's JBR is auto-detected).
|
||||
|
||||
SECURITY: no real keystore password is ever stored in a committed file.
|
||||
key.properties and *.keystore are git-ignored. -GenerateKeystore reads the
|
||||
password ONLY from -KeystorePassword or $env:NETDESK_KEYSTORE_PASSWORD.
|
||||
#>
|
||||
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', 'KeystorePassword',
|
||||
Justification = 'keytool -storepass/-keypass require plaintext; sourced from an env var and never committed.')]
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
# Root of the Android NDK (contains source.properties + toolchains/llvm).
|
||||
[string] $NdkHome = $(if ($env:ANDROID_NDK_HOME) { $env:ANDROID_NDK_HOME } else { 'C:\dev\android-ndk' }),
|
||||
# Android SDK root.
|
||||
[string] $SdkHome = $(if ($env:ANDROID_HOME) { $env:ANDROID_HOME } elseif ($env:ANDROID_SDK_ROOT) { $env:ANDROID_SDK_ROOT } else { Join-Path $env:LOCALAPPDATA 'Android\Sdk' }),
|
||||
# JDK home for keytool + Gradle. Auto-detected (Android Studio JBR) when empty.
|
||||
[string] $JavaHome = $env:JAVA_HOME,
|
||||
# ABIs to build. arm64-v8a is the primary shipping ABI.
|
||||
[string[]] $Abis = @('arm64-v8a', 'armeabi-v7a', 'x86_64'),
|
||||
# Android API level the .so links against. Must be <= the Flutter minSdk.
|
||||
[int] $MinSdk = 21,
|
||||
# Cargo package compiled into the Android .so (the flutter_rust_bridge layer).
|
||||
[string] $RustPackage = 'netdesk-frb',
|
||||
# The crate's [lib] name -> the .so is lib<LibName>.so (what the Dart loader opens).
|
||||
[string] $LibName = 'netdesk_frb',
|
||||
# Which deliverables to produce.
|
||||
[ValidateSet('all', 'apk', 'split', 'aab', 'rust', 'none')]
|
||||
[string] $Target = 'all',
|
||||
# Skip the Rust/cargo-ndk step (reuse whatever .so are already in jniLibs).
|
||||
[switch] $SkipRust,
|
||||
# Create (if missing) the release keystore + key.properties before building.
|
||||
[switch] $GenerateKeystore,
|
||||
# Keystore / key password. NEVER hard-code this - pass it or set the env var.
|
||||
[string] $KeystorePassword = $env:NETDESK_KEYSTORE_PASSWORD,
|
||||
# Key alias inside the keystore.
|
||||
[string] $KeyAlias = 'netdesk',
|
||||
# Path to flutter.bat.
|
||||
[string] $FlutterBat = 'C:\dev\flutter\bin\flutter.bat'
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version 2.0
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Paths
|
||||
# --------------------------------------------------------------------------- #
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$RepoRoot = Split-Path -Parent $ScriptDir
|
||||
$UiDir = Join-Path $RepoRoot 'ui'
|
||||
$AndroidDir = Join-Path $UiDir 'android'
|
||||
$JniLibs = Join-Path $AndroidDir 'app\src\main\jniLibs'
|
||||
$CorePkg = $RustPackage
|
||||
$SoName = "lib$LibName.so"
|
||||
|
||||
# Flutter ABI name -> Rust target triple (for reporting / target checks).
|
||||
$AbiTriple = @{
|
||||
'arm64-v8a' = 'aarch64-linux-android'
|
||||
'armeabi-v7a' = 'armv7-linux-androideabi'
|
||||
'x86_64' = 'x86_64-linux-android'
|
||||
'x86' = 'i686-linux-android'
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Logging
|
||||
# --------------------------------------------------------------------------- #
|
||||
function Write-Step { param([string]$m) Write-Host "`n==> $m" -ForegroundColor Cyan }
|
||||
function Write-Info { param([string]$m) Write-Host " $m" -ForegroundColor Gray }
|
||||
function Write-Ok { param([string]$m) Write-Host " [ok] $m" -ForegroundColor Green }
|
||||
function Write-Warn2{ param([string]$m) Write-Host " [warn] $m" -ForegroundColor Yellow }
|
||||
function Die { param([string]$m) Write-Host "`n[FATAL] $m`n" -ForegroundColor Red; exit 1 }
|
||||
|
||||
function Invoke-Native {
|
||||
param([string]$Exe, [string[]]$ArgList, [string]$WorkDir)
|
||||
if ($WorkDir) { Push-Location $WorkDir }
|
||||
try {
|
||||
Write-Info "$Exe $($ArgList -join ' ')"
|
||||
& $Exe @ArgList
|
||||
if ($LASTEXITCODE -ne 0) { Die "'$Exe' exited with code $LASTEXITCODE" }
|
||||
} finally {
|
||||
if ($WorkDir) { Pop-Location }
|
||||
}
|
||||
}
|
||||
|
||||
$banner = @'
|
||||
_ _ _ ____ _ _ _ _ _
|
||||
| \ | | ___| |_| _ \ ___ ___| | _| | __ _ _ __ __| |_ __ ___ (_) __| |
|
||||
| \| |/ _ \ __| | | |/ _ \/ __| |/ / | / _` | '_ \ / _` | '__/ _ \| |/ _` |
|
||||
| |\ | __/ |_| |_| | __/\__ \ <| |___| (_| | | | | (_| | | | (_) | | (_| |
|
||||
|_| \_|\___|\__|____/ \___||___/_|\_\_____|\__,_|_| |_|\__,_|_| \___/|_|\__,_|
|
||||
'@
|
||||
Write-Host $banner -ForegroundColor Blue
|
||||
Write-Host " NetDesk Android build pipeline - Akronlogic" -ForegroundColor DarkGray
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Toolchain discovery / environment
|
||||
# --------------------------------------------------------------------------- #
|
||||
Write-Step 'Resolving toolchain'
|
||||
|
||||
if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) {
|
||||
Die 'cargo not found on PATH. Install Rust (https://rustup.rs) and re-open the shell.'
|
||||
}
|
||||
Write-Ok "cargo: $((cargo --version) 2>&1)"
|
||||
|
||||
# JAVA_HOME: auto-detect Android Studio's bundled JBR when not provided.
|
||||
if (-not $JavaHome -or -not (Test-Path (Join-Path $JavaHome 'bin'))) {
|
||||
$jbrCandidates = @(
|
||||
'C:\Program Files\Android\Android Studio\jbr',
|
||||
'C:\Program Files\Android\Android Studio\jre',
|
||||
(Join-Path ${env:ProgramFiles} 'Android\Android Studio\jbr')
|
||||
) | Where-Object { $_ -and (Test-Path (Join-Path $_ 'bin\java.exe')) }
|
||||
if ($jbrCandidates) { $JavaHome = $jbrCandidates[0] }
|
||||
}
|
||||
if ($JavaHome -and (Test-Path (Join-Path $JavaHome 'bin\java.exe'))) {
|
||||
$env:JAVA_HOME = $JavaHome
|
||||
$env:PATH = (Join-Path $JavaHome 'bin') + ';' + $env:PATH
|
||||
Write-Ok "JAVA_HOME: $JavaHome"
|
||||
} else {
|
||||
Write-Warn2 'No JDK found (JAVA_HOME unset and no Android Studio JBR). Gradle/keytool may fail.'
|
||||
}
|
||||
|
||||
# Android SDK.
|
||||
if (Test-Path $SdkHome) {
|
||||
$env:ANDROID_HOME = $SdkHome
|
||||
$env:ANDROID_SDK_ROOT = $SdkHome
|
||||
Write-Ok "ANDROID_HOME: $SdkHome"
|
||||
} else {
|
||||
Write-Warn2 "Android SDK not found at $SdkHome (Flutter build apk/aab needs it)."
|
||||
}
|
||||
|
||||
$needRust = (-not $SkipRust) -and ($Target -in @('all', 'apk', 'split', 'aab', 'rust'))
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Android NDK - detection (the script "detects it"; required only for Rust)
|
||||
# --------------------------------------------------------------------------- #
|
||||
function Test-Ndk {
|
||||
param([string]$Path)
|
||||
if (-not $Path) { return $false }
|
||||
if (-not (Test-Path (Join-Path $Path 'source.properties'))) { return $false }
|
||||
if (-not (Test-Path (Join-Path $Path 'toolchains\llvm\prebuilt'))) { return $false }
|
||||
return $true
|
||||
}
|
||||
|
||||
$ndkOk = Test-Ndk $NdkHome
|
||||
if ($needRust) {
|
||||
Write-Step 'Locating Android NDK'
|
||||
if ($ndkOk) {
|
||||
$rev = (Select-String -Path (Join-Path $NdkHome 'source.properties') -Pattern 'Pkg.Revision\s*=\s*(.+)' ).Matches.Groups[1].Value.Trim()
|
||||
$env:ANDROID_NDK_HOME = $NdkHome
|
||||
$env:ANDROID_NDK_ROOT = $NdkHome
|
||||
Write-Ok "NDK $rev at $NdkHome"
|
||||
} else {
|
||||
Die @"
|
||||
Android NDK not found (or incomplete) at:
|
||||
$NdkHome
|
||||
|
||||
Expected '$NdkHome\source.properties' and '$NdkHome\toolchains\llvm\prebuilt'.
|
||||
The NDK may still be downloading/extracting. When it is in place, re-run:
|
||||
|
||||
powershell -ExecutionPolicy Bypass -File packaging\build-android.ps1
|
||||
|
||||
Or point at another location: -NdkHome <path> / `$env:ANDROID_NDK_HOME
|
||||
Or skip the Rust step to build Flutter only: -SkipRust
|
||||
"@
|
||||
}
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Rust -> per-ABI .so via cargo-ndk
|
||||
# --------------------------------------------------------------------------- #
|
||||
if ($needRust) {
|
||||
Write-Step 'Preparing Rust Android targets'
|
||||
|
||||
# cargo-ndk (auto-install if missing).
|
||||
if (-not (Get-Command cargo-ndk -ErrorAction SilentlyContinue)) {
|
||||
Write-Warn2 'cargo-ndk not found - installing (cargo install cargo-ndk)...'
|
||||
Invoke-Native 'cargo' @('install', 'cargo-ndk')
|
||||
}
|
||||
Write-Ok "cargo-ndk: $((cargo ndk --version) 2>&1)"
|
||||
|
||||
# rustup std for each requested ABI (auto-add if missing).
|
||||
$installed = (rustup target list --installed) 2>&1
|
||||
foreach ($abi in $Abis) {
|
||||
$triple = $AbiTriple[$abi]
|
||||
if (-not $triple) { Die "Unknown ABI '$abi'. Valid: $($AbiTriple.Keys -join ', ')" }
|
||||
if ($installed -notcontains $triple) {
|
||||
Write-Warn2 "rustup target '$triple' missing - adding..."
|
||||
Invoke-Native 'rustup' @('target', 'add', $triple)
|
||||
} else {
|
||||
Write-Ok "rust target present: $triple"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Step "Building $CorePkg -> $SoName [$($Abis -join ', ')] (API $MinSdk)"
|
||||
New-Item -ItemType Directory -Force -Path $JniLibs | Out-Null
|
||||
|
||||
# cargo-ndk writes <JniLibs>/<abi>/<SoName> directly (that IS the "copy").
|
||||
# cargo ndk -t <abi>... -o <jniLibs> --platform <api> build --release --package <RustPackage> --lib
|
||||
$ndkArgs = @()
|
||||
foreach ($abi in $Abis) { $ndkArgs += @('-t', $abi) }
|
||||
$ndkArgs += '-o'; $ndkArgs += $JniLibs
|
||||
$ndkArgs += '--platform'; $ndkArgs += "$MinSdk"
|
||||
$ndkArgs += 'build'; $ndkArgs += '--release'
|
||||
$ndkArgs += '--package'; $ndkArgs += $CorePkg
|
||||
$ndkArgs += '--lib'
|
||||
|
||||
Invoke-Native 'cargo' (@('ndk') + $ndkArgs) $RepoRoot
|
||||
|
||||
Write-Step 'Verifying .so artifacts'
|
||||
$allOk = $true
|
||||
foreach ($abi in $Abis) {
|
||||
$so = Join-Path $JniLibs "$abi\$SoName"
|
||||
if (Test-Path $so) {
|
||||
$kb = [math]::Round((Get-Item $so).Length / 1KB)
|
||||
Write-Ok "$abi/$SoName ($kb KB)"
|
||||
} else {
|
||||
Write-Warn2 "MISSING: $abi/$SoName"
|
||||
$allOk = $false
|
||||
}
|
||||
}
|
||||
if (-not $allOk) { Die 'One or more ABIs failed to produce a .so (see cargo output above).' }
|
||||
|
||||
# ----------------------------------------------------------------------- #
|
||||
# Bundle the shared C++ STL beside the engine. The Rust cdylib links
|
||||
# against libc++_shared (pulled in by the openh264 C++ build), and Android
|
||||
# devices do NOT ship it — without this copy the app dies at startup with:
|
||||
# dlopen failed: library "libc++_shared.so" not found
|
||||
# (needed by .../lib/<abi>/libnetdesk_frb.so)
|
||||
# NOTE: the sysroot folder for armeabi-v7a is 'arm-linux-androideabi'
|
||||
# (NOT the Rust triple 'armv7-linux-androideabi').
|
||||
# ----------------------------------------------------------------------- #
|
||||
Write-Step 'Bundling libc++_shared.so per ABI'
|
||||
$SysrootLib = Join-Path $NdkHome 'toolchains\llvm\prebuilt\windows-x86_64\sysroot\usr\lib'
|
||||
$AbiSysDir = @{
|
||||
'arm64-v8a' = 'aarch64-linux-android'
|
||||
'armeabi-v7a' = 'arm-linux-androideabi'
|
||||
'x86_64' = 'x86_64-linux-android'
|
||||
}
|
||||
foreach ($abi in $Abis) {
|
||||
$src = Join-Path $SysrootLib "$($AbiSysDir[$abi])\libc++_shared.so"
|
||||
if (-not (Test-Path $src)) { Die "libc++_shared.so not found for ${abi}: $src" }
|
||||
Copy-Item $src (Join-Path $JniLibs "$abi\libc++_shared.so") -Force
|
||||
$kb = [math]::Round((Get-Item (Join-Path $JniLibs "$abi\libc++_shared.so")).Length / 1KB)
|
||||
Write-Ok "$abi/libc++_shared.so bundled ($kb KB)"
|
||||
}
|
||||
} else {
|
||||
Write-Step 'Skipping Rust step (-SkipRust or -Target none)'
|
||||
}
|
||||
|
||||
if ($Target -eq 'rust' -or $Target -eq 'none') {
|
||||
Write-Step 'Done (Rust-only target requested).'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Release signing (optional keystore provisioning)
|
||||
# --------------------------------------------------------------------------- #
|
||||
$KeyPropsFile = Join-Path $AndroidDir 'key.properties'
|
||||
$KeystoreFile = Join-Path $AndroidDir 'app\netdesk-release.keystore'
|
||||
|
||||
if ($GenerateKeystore) {
|
||||
Write-Step 'Provisioning release signing keystore'
|
||||
if (Test-Path $KeystoreFile) {
|
||||
Write-Ok "Keystore already exists: $KeystoreFile (not regenerating)"
|
||||
} else {
|
||||
if ([string]::IsNullOrWhiteSpace($KeystorePassword)) {
|
||||
Die @'
|
||||
-GenerateKeystore was requested but no password was supplied.
|
||||
Set it WITHOUT hard-coding it anywhere committed, e.g.:
|
||||
|
||||
$env:NETDESK_KEYSTORE_PASSWORD = '<your-strong-password>'
|
||||
... -GenerateKeystore
|
||||
|
||||
or pass -KeystorePassword '<your-strong-password>'.
|
||||
'@
|
||||
}
|
||||
$keytool = $null
|
||||
foreach ($c in @(
|
||||
(Join-Path $env:JAVA_HOME 'bin\keytool.exe'),
|
||||
'C:\Program Files\Android\Android Studio\jbr\bin\keytool.exe')) {
|
||||
if ($c -and (Test-Path $c)) { $keytool = $c; break }
|
||||
}
|
||||
if (-not $keytool) {
|
||||
$gc = Get-Command keytool -ErrorAction SilentlyContinue
|
||||
if ($gc) { $keytool = $gc.Source }
|
||||
}
|
||||
if (-not $keytool) { Die 'keytool not found (install a JDK or Android Studio).' }
|
||||
|
||||
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $KeystoreFile) | Out-Null
|
||||
Invoke-Native $keytool @(
|
||||
'-genkeypair', '-v',
|
||||
'-keystore', $KeystoreFile,
|
||||
'-alias', $KeyAlias,
|
||||
'-keyalg', 'RSA', '-keysize', '4096',
|
||||
'-validity', '10000',
|
||||
'-storepass', $KeystorePassword,
|
||||
'-keypass', $KeystorePassword,
|
||||
'-dname', 'CN=NetDesk, O=Akronlogic, OU=Release, C=DE'
|
||||
)
|
||||
Write-Ok "Created keystore: $KeystoreFile"
|
||||
}
|
||||
|
||||
# Write key.properties (git-ignored; holds the local password).
|
||||
@"
|
||||
# NetDesk release signing - LOCAL ONLY, never commit (git-ignored).
|
||||
# Regenerate the keystore with: build-android.ps1 -GenerateKeystore
|
||||
storePassword=$KeystorePassword
|
||||
keyPassword=$KeystorePassword
|
||||
keyAlias=$KeyAlias
|
||||
storeFile=$($KeystoreFile -replace '\\','/')
|
||||
"@ | Set-Content -Encoding ASCII -Path $KeyPropsFile
|
||||
Write-Ok "Wrote $KeyPropsFile"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $KeyPropsFile)) {
|
||||
Write-Warn2 'No key.properties found - release artifacts will be DEBUG-signed.'
|
||||
Write-Warn2 'Run with -GenerateKeystore (and a password) to produce upload-signed builds.'
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Flutter build
|
||||
# --------------------------------------------------------------------------- #
|
||||
if (-not (Test-Path $FlutterBat)) { Die "flutter.bat not found at $FlutterBat (use -FlutterBat)." }
|
||||
if (-not (Test-Path $AndroidDir)) {
|
||||
Die "Flutter android/ project missing at $AndroidDir. Run: flutter create --platforms=android . (in ui/)"
|
||||
}
|
||||
|
||||
Write-Step 'flutter pub get'
|
||||
Invoke-Native $FlutterBat @('pub', 'get') $UiDir
|
||||
|
||||
function Invoke-FlutterBuild {
|
||||
param([string[]]$ArgList, [string]$Label)
|
||||
Write-Step "flutter build $Label"
|
||||
Invoke-Native $FlutterBat (@('build') + $ArgList) $UiDir
|
||||
Write-Ok "$Label done"
|
||||
}
|
||||
|
||||
switch ($Target) {
|
||||
'apk' { Invoke-FlutterBuild @('apk', '--release') 'apk (fat)' }
|
||||
'split' { Invoke-FlutterBuild @('apk', '--release', '--split-per-abi') 'apk (split-per-abi)' }
|
||||
'aab' { Invoke-FlutterBuild @('appbundle', '--release') 'appbundle (.aab)' }
|
||||
'all' {
|
||||
Invoke-FlutterBuild @('apk', '--release') 'apk (fat)'
|
||||
Invoke-FlutterBuild @('apk', '--release', '--split-per-abi') 'apk (split-per-abi)'
|
||||
Invoke-FlutterBuild @('appbundle', '--release') 'appbundle (.aab)'
|
||||
}
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Report artifacts
|
||||
# --------------------------------------------------------------------------- #
|
||||
Write-Step 'Artifacts'
|
||||
$outApk = Join-Path $UiDir 'build\app\outputs\flutter-apk'
|
||||
$outAab = Join-Path $UiDir 'build\app\outputs\bundle\release'
|
||||
foreach ($dir in @($outApk, $outAab)) {
|
||||
if (Test-Path $dir) {
|
||||
Get-ChildItem $dir -Filter '*.ap*' -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
$mb = [math]::Round($_.Length / 1MB, 2)
|
||||
Write-Host (" {0,-34} {1,8} MB" -f $_.Name, $mb) -ForegroundColor Green
|
||||
Write-Host (" {0}" -f $_.FullName) -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-Host "`n==> NetDesk Android build complete.`n" -ForegroundColor Cyan
|
||||
@@ -0,0 +1,58 @@
|
||||
<#
|
||||
NetDesk by Akronlogic -- white-label / custom-client generator.
|
||||
|
||||
Produces a pre-configured, re-brandable portable client by RENAMING the exe so
|
||||
it self-configures on first launch (no rebuild, no per-brand signing key
|
||||
needed — the single signed NetDesk.exe is reused):
|
||||
|
||||
powershell -File build-brand.ps1 -Name AcmeRemote -Server vpn.acme.io -Key ACME9 [-Port 8443]
|
||||
|
||||
Output: dist\brand-<Name>\<Name>-server=<Server>[,port=<Port>][,key=<Key>].exe
|
||||
+ the runtime DLLs. Ship the folder as-is; on first launch the client writes
|
||||
the relay + registration key into config.json (see core::brand +
|
||||
viewer apply_filename_selfconfig). Logo / palette / title theming and a signed
|
||||
MSI with a reseller cert are the remaining white-label steps (see BUILD-QUEUE).
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$Name,
|
||||
[Parameter(Mandatory)][string]$Server,
|
||||
[string]$Key = "",
|
||||
[string]$Port = "",
|
||||
[string]$MingwBin = "C:\mingw-netdesk\mingw64\bin",
|
||||
[string]$OutDir = "",
|
||||
[switch]$SkipStage
|
||||
)
|
||||
$ErrorActionPreference = "Stop"
|
||||
$Pkg = $PSScriptRoot
|
||||
$Stage = Join-Path $Pkg "dist\NetDesk-Portable"
|
||||
|
||||
# 1. stage the signed portable payload unless told to reuse it.
|
||||
if (-not $SkipStage) {
|
||||
& (Join-Path $Pkg "build-portable.ps1") -MingwBin $MingwBin -SkipBuild
|
||||
}
|
||||
if (-not (Test-Path (Join-Path $Stage "NetDesk.exe"))) {
|
||||
throw "staged payload missing: $Stage (run build-portable.ps1 first, or omit -SkipStage)"
|
||||
}
|
||||
|
||||
# 2. compose the self-configuring exe name.
|
||||
$cfg = "server=$Server"
|
||||
if ($Port) { $cfg += ",port=$Port" }
|
||||
if ($Key) { $cfg += ",key=$Key" }
|
||||
$exeName = "$Name-$cfg.exe"
|
||||
|
||||
if (-not $OutDir) { $OutDir = Join-Path $Pkg ("dist\brand-" + $Name) }
|
||||
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
|
||||
|
||||
# 3. the single signed NetDesk.exe is reused verbatim, just renamed; the runtime
|
||||
# DLLs travel with it. (The signature stays valid — we rename, not patch.)
|
||||
Copy-Item (Join-Path $Stage "NetDesk.exe") (Join-Path $OutDir $exeName) -Force
|
||||
foreach ($dll in "libgcc_s_seh-1.dll","libstdc++-6.dll","libwinpthread-1.dll") {
|
||||
$src = Join-Path $Stage $dll
|
||||
if (Test-Path $src) { Copy-Item $src (Join-Path $OutDir $dll) -Force }
|
||||
}
|
||||
|
||||
$scheme = "wss://$Server"
|
||||
if ($Port) { $scheme = "wss://${Server}:$Port" }
|
||||
Write-Host "white-label client -> $OutDir\$exeName" -ForegroundColor Green
|
||||
Write-Host " pre-pointed at $scheme/ws; self-configures config.json on first launch."
|
||||
@@ -0,0 +1,145 @@
|
||||
<#
|
||||
NetDesk by Akronlogic -- INSTALLER build script.
|
||||
|
||||
1. Runs build-portable.ps1 to build the release viewer and stage the files
|
||||
(dist\NetDesk-Portable\).
|
||||
2. Detects makensis (NSIS). If found, compiles netdesk.nsi into
|
||||
dist\NetDesk-Setup-win64.exe. If NOT found, prints install instructions
|
||||
and still leaves the staging folder ready.
|
||||
3. Code-signs NetDesk.exe (in the staging folder) and the setup .exe via
|
||||
certs\sign-release.ps1, when signing is available.
|
||||
|
||||
Usage:
|
||||
powershell -ExecutionPolicy Bypass -File build-installer.ps1
|
||||
powershell -ExecutionPolicy Bypass -File build-installer.ps1 -SkipBuild
|
||||
powershell -ExecutionPolicy Bypass -File build-installer.ps1 -NoSign
|
||||
powershell -ExecutionPolicy Bypass -File build-installer.ps1 -Thumbprint <THUMB>
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$MingwBin = "C:\mingw-netdesk\mingw64\bin",
|
||||
[string]$Thumbprint = "YOUR_CODE_SIGNING_CERT_THUMBPRINT",
|
||||
[switch]$SkipBuild,
|
||||
[switch]$NoSign
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# ---- paths -----------------------------------------------------------------
|
||||
$PkgDir = $PSScriptRoot
|
||||
$RootDir = Split-Path -Parent $PkgDir
|
||||
$DistDir = Join-Path $PkgDir "dist"
|
||||
$StageDir = Join-Path $DistDir "NetDesk-Portable"
|
||||
$NsiScript = Join-Path $PkgDir "netdesk.nsi"
|
||||
$SetupExe = Join-Path $DistDir "NetDesk-Setup-win64.exe"
|
||||
$SignPs1 = Join-Path $RootDir "certs\sign-release.ps1"
|
||||
$BrandIco = Join-Path $PkgDir "netdesk.ico" # branded setup-exe icon
|
||||
$AssetIco = Join-Path $RootDir "assets\icon.ico" # source NetDesk mark
|
||||
|
||||
Write-Host "=== NetDesk installer build (Akronlogic) ===" -ForegroundColor Cyan
|
||||
|
||||
# ---- 1. stage via build-portable.ps1 --------------------------------------
|
||||
Write-Host "`n[1/4] staging portable payload" -ForegroundColor Yellow
|
||||
$portableArgs = @{ MingwBin = $MingwBin }
|
||||
if ($SkipBuild) { $portableArgs.SkipBuild = $true }
|
||||
& (Join-Path $PkgDir "build-portable.ps1") @portableArgs
|
||||
if ($LASTEXITCODE) { throw "build-portable.ps1 failed (exit $LASTEXITCODE)" }
|
||||
if (-not (Test-Path $StageDir)) { throw "staging folder missing: $StageDir" }
|
||||
|
||||
# ---- sign the staged NetDesk.exe BEFORE packaging it into the installer ----
|
||||
function Invoke-Sign([string[]]$Files) {
|
||||
if ($NoSign) { Write-Host " -NoSign set: skipping signing." -ForegroundColor DarkYellow; return }
|
||||
if (-not (Test-Path $SignPs1)) { Write-Host " signing helper not found ($SignPs1) -- skipping." -ForegroundColor DarkYellow; return }
|
||||
$signtool = Get-Command signtool.exe -ErrorAction SilentlyContinue
|
||||
foreach ($f in $Files) {
|
||||
if (-not (Test-Path $f)) { continue }
|
||||
Write-Host " signing: $f" -ForegroundColor Gray
|
||||
try {
|
||||
if ($signtool) {
|
||||
& $signtool.Source sign /sha1 $Thumbprint /fd SHA256 /tr "http://timestamp.sectigo.com" /td SHA256 "$f"
|
||||
if ($LASTEXITCODE) { Write-Host " signtool returned $LASTEXITCODE (continuing)" -ForegroundColor DarkYellow }
|
||||
} else {
|
||||
# Fall back to Set-AuthenticodeSignature via the project helper logic.
|
||||
$cert = Get-ChildItem Cert:\CurrentUser\My | Where-Object { $_.Thumbprint -eq $Thumbprint } | Select-Object -First 1
|
||||
if (-not $cert) { Write-Host " cert $Thumbprint not in CurrentUser\My -- skipping." -ForegroundColor DarkYellow; continue }
|
||||
$r = Set-AuthenticodeSignature -FilePath $f -Certificate $cert -HashAlgorithm SHA256 -TimestampServer "http://timestamp.sectigo.com"
|
||||
Write-Host (" sign: {0}" -f $r.Status) -ForegroundColor Gray
|
||||
}
|
||||
} catch {
|
||||
Write-Host " signing failed: $($_.Exception.Message) (continuing)" -ForegroundColor DarkYellow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`n[2/4] signing staged NetDesk.exe" -ForegroundColor Yellow
|
||||
# Run the official project helper on the release tree (signs netdesk-viewer.exe
|
||||
# etc. exactly as Akronlogic ships them). Non-fatal if the cert isn't present.
|
||||
if (-not $NoSign -and (Test-Path $SignPs1)) {
|
||||
try {
|
||||
& powershell -NoProfile -ExecutionPolicy Bypass -File $SignPs1 -Thumbprint $Thumbprint
|
||||
} catch {
|
||||
Write-Host " sign-release.ps1 note: $($_.Exception.Message) (continuing)" -ForegroundColor DarkYellow
|
||||
}
|
||||
}
|
||||
# The staged NetDesk.exe is a renamed COPY, so it needs its own signature using
|
||||
# the same certificate + timestamp mechanism that sign-release.ps1 uses.
|
||||
Invoke-Sign @( (Join-Path $StageDir "NetDesk.exe") )
|
||||
|
||||
# Re-zip the portable artifact so it, too, ships the now-signed NetDesk.exe
|
||||
# (build-portable.ps1 zipped the staging folder before this signing step).
|
||||
if (-not $NoSign) {
|
||||
$zip = Join-Path $DistDir "NetDesk-Portable-win64.zip"
|
||||
if (Test-Path $zip) { Remove-Item $zip -Force }
|
||||
Compress-Archive -Path (Join-Path $StageDir "*") -DestinationPath $zip -CompressionLevel Optimal
|
||||
Write-Host " re-zipped portable with signed NetDesk.exe -> $zip" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
# ---- 3. compile the installer with makensis -------------------------------
|
||||
Write-Host "`n[3/4] locating makensis (NSIS)" -ForegroundColor Yellow
|
||||
$makensis = Get-Command makensis.exe -ErrorAction SilentlyContinue
|
||||
if (-not $makensis) {
|
||||
foreach ($p in @("$env:ProgramFiles\NSIS\makensis.exe", "${env:ProgramFiles(x86)}\NSIS\makensis.exe")) {
|
||||
if (Test-Path $p) { $makensis = Get-Item $p; break }
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $makensis) {
|
||||
Write-Host ""
|
||||
Write-Host "makensis (NSIS) is NOT installed -- the installer .exe was NOT built." -ForegroundColor Red
|
||||
Write-Host "The portable zip and the installer staging folder are ready." -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "Install NSIS, then re-run this script:" -ForegroundColor Yellow
|
||||
Write-Host " winget install --id NSIS.NSIS -e"
|
||||
Write-Host " or download from https://nsis.sourceforge.io/Download"
|
||||
Write-Host ""
|
||||
Write-Host "After installing, build the installer manually with:" -ForegroundColor Yellow
|
||||
Write-Host (" makensis /DSTAGEDIR={0} /DOUTFILE={1} `"{2}`"" -f $StageDir, $SetupExe, $NsiScript)
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host " found: $($makensis.Source)" -ForegroundColor Gray
|
||||
if (Test-Path $SetupExe) { Remove-Item $SetupExe -Force }
|
||||
|
||||
# Brand the setup .exe with the green NetDesk icon (copy the asset next to the
|
||||
# .nsi and pass its absolute path as /DBRANDICON).
|
||||
$nsisArgs = @("/DSTAGEDIR=$StageDir", "/DOUTFILE=$SetupExe")
|
||||
if (Test-Path $AssetIco) {
|
||||
Copy-Item $AssetIco $BrandIco -Force
|
||||
$nsisArgs += "/DBRANDICON=$BrandIco"
|
||||
Write-Host " branding setup icon: $BrandIco" -ForegroundColor Gray
|
||||
} else {
|
||||
Write-Host " asset icon not found ($AssetIco) -- setup will use the default NSIS icon." -ForegroundColor DarkYellow
|
||||
}
|
||||
$nsisArgs += "$NsiScript"
|
||||
& $makensis.Source @nsisArgs
|
||||
if ($LASTEXITCODE) { throw "makensis failed (exit $LASTEXITCODE)" }
|
||||
if (-not (Test-Path $SetupExe)) { throw "makensis reported success but $SetupExe is missing" }
|
||||
|
||||
# ---- 4. sign the setup .exe ------------------------------------------------
|
||||
Write-Host "`n[4/4] signing the setup installer" -ForegroundColor Yellow
|
||||
Invoke-Sign @( $SetupExe )
|
||||
|
||||
$si = Get-Item $SetupExe
|
||||
Write-Host "`nInstaller build complete." -ForegroundColor Green
|
||||
Write-Host (" setup : {0}" -f $si.FullName)
|
||||
Write-Host (" size : {0:N0} bytes ({1:N2} MB)" -f $si.Length, ($si.Length/1MB))
|
||||
@@ -0,0 +1,241 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# NetDesk by Akronlogic -- iOS build (Flutter + Rust core).
|
||||
#
|
||||
# Runs on macOS ONLY (needs Xcode + the iOS SDKs). It:
|
||||
# 1. adds the Rust iOS targets (aarch64-apple-ios + aarch64-apple-ios-sim)
|
||||
# 2. builds the Rust core static lib for device AND simulator
|
||||
# 3. assembles NetDeskCore.xcframework (xcodebuild -create-xcframework)
|
||||
# 4. ensures ui/ios/ exists (flutter create --platforms=ios .)
|
||||
# 5. wires the xcframework into the Flutter iOS project (CocoaPods podspec)
|
||||
# 6. flutter build ipa (signed with your Apple team)
|
||||
#
|
||||
# The Rust FFI static lib comes from the workspace `core` crate
|
||||
# (package netdesk-core, lib netdesk_core -> libnetdesk_core.a). When the
|
||||
# Phase-3 flutter_rust_bridge wrapper crate lands under a different name
|
||||
# (e.g. "netdesk-frb"), point the script at it with:
|
||||
# CORE_CRATE=netdesk-frb LIB_NAME=netdesk_frb ./build-ios.sh ...
|
||||
#
|
||||
# See docs/build-ios.md for prerequisites (Apple Developer Program, signing
|
||||
# cert + provisioning profile) and the full walkthrough.
|
||||
#
|
||||
# Usage:
|
||||
# packaging/build-ios.sh --team ABCDE12345
|
||||
# packaging/build-ios.sh --team ABCDE12345 --method ad-hoc --org com.akronlogic
|
||||
# packaging/build-ios.sh --skip-ipa # just build the xcframework
|
||||
# packaging/build-ios.sh --help
|
||||
#
|
||||
# Env overrides: CORE_CRATE LIB_NAME DEVICE_TARGET SIM_TARGET IOS_MIN
|
||||
# FLUTTER TEAM_ID EXPORT_METHOD FLUTTER_ORG
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
# ---- configuration (env-overridable) ---------------------------------------
|
||||
CORE_CRATE="${CORE_CRATE:-netdesk-core}" # cargo package to build
|
||||
LIB_NAME="${LIB_NAME:-netdesk_core}" # -> lib<LIB_NAME>.a
|
||||
DEVICE_TARGET="${DEVICE_TARGET:-aarch64-apple-ios}"
|
||||
SIM_TARGET="${SIM_TARGET:-aarch64-apple-ios-sim}" # Intel Macs: x86_64-apple-ios
|
||||
IOS_MIN="${IOS_MIN:-13.0}"
|
||||
FLUTTER="${FLUTTER:-flutter}"
|
||||
FLUTTER_ORG="${FLUTTER_ORG:-space.netdesk}" # -> bundle id <org>.netdesk
|
||||
EXPORT_METHOD="${EXPORT_METHOD:-app-store}" # app-store | ad-hoc | development | enterprise
|
||||
TEAM_ID="${TEAM_ID:-${DEVELOPMENT_TEAM:-}}" # 10-char Apple Team ID
|
||||
XCFRAMEWORK="NetDeskCore.xcframework"
|
||||
|
||||
SKIP_IPA=0
|
||||
|
||||
# ---- args ------------------------------------------------------------------
|
||||
usage() { sed -n '3,29p' "$0" | sed 's/^# \{0,1\}//'; exit "${1:-0}"; }
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--team) TEAM_ID="${2:?--team needs an Apple Team ID}"; shift 2 ;;
|
||||
--method) EXPORT_METHOD="${2:?--method needs a value}"; shift 2 ;;
|
||||
--org) FLUTTER_ORG="${2:?--org needs a value}"; shift 2 ;;
|
||||
--skip-ipa|--xcframework-only) SKIP_IPA=1; shift ;;
|
||||
-h|--help) usage 0 ;;
|
||||
*) echo "unknown argument: $1" >&2; usage 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ---- paths -----------------------------------------------------------------
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
UI="$ROOT/ui"
|
||||
IOSDIR="$UI/ios"
|
||||
TEMPLATES="$SCRIPT_DIR/ios"
|
||||
DIST="$SCRIPT_DIR/dist/ios"
|
||||
DEV_LIB="$ROOT/target/$DEVICE_TARGET/release/lib$LIB_NAME.a"
|
||||
SIM_LIB="$ROOT/target/$SIM_TARGET/release/lib$LIB_NAME.a"
|
||||
|
||||
say() { printf '\033[36m==> %s\033[0m\n' "$*"; }
|
||||
ok() { printf '\033[32m %s\033[0m\n' "$*"; }
|
||||
die() { printf '\033[31mERROR: %s\033[0m\n' "$*" >&2; exit 1; }
|
||||
|
||||
cat <<BANNER
|
||||
=== NetDesk iOS build (Akronlogic) ===
|
||||
root : $ROOT
|
||||
crate : $CORE_CRATE (lib$LIB_NAME.a)
|
||||
targets: $DEVICE_TARGET + $SIM_TARGET
|
||||
flutter: $FLUTTER org: $FLUTTER_ORG ios-min: $IOS_MIN
|
||||
ipa : $([ "$SKIP_IPA" = 1 ] && echo "SKIPPED (--skip-ipa)" || echo "method=$EXPORT_METHOD team=${TEAM_ID:-<unset>}")
|
||||
BANNER
|
||||
|
||||
# ---- 0. preflight ----------------------------------------------------------
|
||||
say "0/6 preflight"
|
||||
[ "$(uname -s)" = "Darwin" ] || die "iOS builds require macOS. Run this on the Mac."
|
||||
command -v rustup >/dev/null || die "rustup not found. Install Rust: https://rustup.rs"
|
||||
command -v cargo >/dev/null || die "cargo not found on PATH (try: source \$HOME/.cargo/env)"
|
||||
command -v xcrun >/dev/null || die "xcrun not found. Install Xcode + command line tools (xcode-select --install)."
|
||||
xcode-select -p >/dev/null 2>&1 || die "no active Xcode. Run: sudo xcode-select -s /Applications/Xcode.app"
|
||||
command -v "$FLUTTER" >/dev/null || die "flutter not found. Install the Flutter SDK and add it to PATH (or set FLUTTER=/path/to/flutter)."
|
||||
if [ "$SKIP_IPA" = 0 ] && [ -z "$TEAM_ID" ]; then
|
||||
die "no Apple Team ID. Pass --team ABCDE12345 (or TEAM_ID=...), or use --skip-ipa to only build the xcframework."
|
||||
fi
|
||||
ok "macOS $(sw_vers -productVersion 2>/dev/null || true), Xcode $(xcodebuild -version 2>/dev/null | head -1 || true)"
|
||||
|
||||
# ---- 1. rust ios targets ---------------------------------------------------
|
||||
say "1/6 rustup targets"
|
||||
rustup target add "$DEVICE_TARGET" "$SIM_TARGET"
|
||||
ok "$DEVICE_TARGET, $SIM_TARGET installed"
|
||||
|
||||
# ---- 2. build the rust core static lib for both targets --------------------
|
||||
say "2/6 cargo build (release, staticlib) for device + simulator"
|
||||
# Match Flutter's deployment target so the slices are link-compatible.
|
||||
export IPHONEOS_DEPLOYMENT_TARGET="$IOS_MIN"
|
||||
( cd "$ROOT" && cargo build --release -p "$CORE_CRATE" --target "$DEVICE_TARGET" )
|
||||
( cd "$ROOT" && cargo build --release -p "$CORE_CRATE" --target "$SIM_TARGET" )
|
||||
[ -f "$DEV_LIB" ] || die "expected device lib not found: $DEV_LIB"
|
||||
[ -f "$SIM_LIB" ] || die "expected simulator lib not found: $SIM_LIB"
|
||||
ok "device: $DEV_LIB"
|
||||
ok "sim : $SIM_LIB"
|
||||
|
||||
# ---- 3. assemble the xcframework -------------------------------------------
|
||||
say "3/6 assemble $XCFRAMEWORK"
|
||||
mkdir -p "$DIST"
|
||||
rm -rf "$DIST/$XCFRAMEWORK" # xcodebuild refuses to overwrite
|
||||
# No -headers yet: the C ABI header (cbindgen over core/src/ffi.rs) is Phase 3.
|
||||
# When it exists, add: -headers "$ROOT/target/include" to each -library pair.
|
||||
xcodebuild -create-xcframework \
|
||||
-library "$DEV_LIB" \
|
||||
-library "$SIM_LIB" \
|
||||
-output "$DIST/$XCFRAMEWORK"
|
||||
ok "built $DIST/$XCFRAMEWORK"
|
||||
|
||||
if [ "$SKIP_IPA" = 1 ]; then
|
||||
# Still stage it into ui/ios if that project already exists, so a later
|
||||
# `flutter build ipa` picks up the fresh binary.
|
||||
if [ -d "$IOSDIR" ]; then
|
||||
cp -R "$DIST/$XCFRAMEWORK" "$IOSDIR/"
|
||||
ok "staged into $IOSDIR/$XCFRAMEWORK"
|
||||
fi
|
||||
say "done (--skip-ipa). xcframework at: $DIST/$XCFRAMEWORK"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ---- 4. ensure the Flutter iOS project exists ------------------------------
|
||||
say "4/6 ensure ui/ios Flutter project"
|
||||
if [ ! -d "$IOSDIR" ]; then
|
||||
ok "ui/ios missing — generating with flutter create"
|
||||
( cd "$UI" && "$FLUTTER" create --org "$FLUTTER_ORG" --project-name netdesk --platforms=ios . )
|
||||
else
|
||||
ok "ui/ios already present — leaving it in place"
|
||||
fi
|
||||
( cd "$UI" && "$FLUTTER" pub get )
|
||||
|
||||
# ---- 5. wire the xcframework into ui/ios (CocoaPods) -----------------------
|
||||
say "5/6 wire xcframework into the iOS project"
|
||||
cp -R "$DIST/$XCFRAMEWORK" "$IOSDIR/"
|
||||
ok "copied $XCFRAMEWORK into ui/ios"
|
||||
|
||||
cp "$TEMPLATES/netdesk_core.podspec" "$IOSDIR/netdesk_core.podspec"
|
||||
ok "installed ui/ios/netdesk_core.podspec"
|
||||
|
||||
# Podfile: install the template if there is none, otherwise inject the pod line.
|
||||
if [ ! -f "$IOSDIR/Podfile" ]; then
|
||||
cp "$TEMPLATES/Podfile" "$IOSDIR/Podfile"
|
||||
ok "installed ui/ios/Podfile (from template)"
|
||||
else
|
||||
# Uncomment any commented `platform :ios` line so it matches the xcframework.
|
||||
if grep -qE '^\s*#\s*platform :ios' "$IOSDIR/Podfile"; then
|
||||
sed -i '' -E "s/^\s*#\s*platform :ios.*/platform :ios, '$IOS_MIN'/" "$IOSDIR/Podfile"
|
||||
fi
|
||||
# Inject the netdesk_core pod into the Runner target if it is not there yet.
|
||||
if ! grep -q "pod 'netdesk_core'" "$IOSDIR/Podfile"; then
|
||||
awk '
|
||||
/target '\''Runner'\'' do/ && !done {
|
||||
print
|
||||
print " # NetDesk Rust core (added by packaging/build-ios.sh)."
|
||||
print " pod '\''netdesk_core'\'', :path => '\''.'\''"
|
||||
done = 1
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
' "$IOSDIR/Podfile" > "$IOSDIR/Podfile.tmp" && mv "$IOSDIR/Podfile.tmp" "$IOSDIR/Podfile"
|
||||
ok "injected pod 'netdesk_core' into existing Podfile"
|
||||
else
|
||||
ok "Podfile already references netdesk_core"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Signing for the archive step: set the team + automatic signing via the
|
||||
# Flutter xcconfig layer (applies because a fresh Runner target has no explicit
|
||||
# DEVELOPMENT_TEAM in project.pbxproj). Idempotent.
|
||||
for cfg in Release Debug; do
|
||||
XC="$IOSDIR/Flutter/$cfg.xcconfig"
|
||||
[ -f "$XC" ] || continue
|
||||
if ! grep -q '^DEVELOPMENT_TEAM' "$XC"; then
|
||||
{
|
||||
echo ""
|
||||
echo "// NetDesk signing (added by packaging/build-ios.sh)"
|
||||
echo "DEVELOPMENT_TEAM = $TEAM_ID"
|
||||
echo "CODE_SIGN_STYLE = Automatic"
|
||||
} >> "$XC"
|
||||
ok "set DEVELOPMENT_TEAM in Flutter/$cfg.xcconfig"
|
||||
else
|
||||
sed -i '' -E "s/^DEVELOPMENT_TEAM.*/DEVELOPMENT_TEAM = $TEAM_ID/" "$XC"
|
||||
ok "updated DEVELOPMENT_TEAM in Flutter/$cfg.xcconfig"
|
||||
fi
|
||||
done
|
||||
|
||||
# ExportOptions.plist consumed by `flutter build ipa` for the export phase.
|
||||
cat > "$IOSDIR/ExportOptions.plist" <<PLIST
|
||||
<?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>method</key><string>$EXPORT_METHOD</string>
|
||||
<key>teamID</key><string>$TEAM_ID</string>
|
||||
<key>signingStyle</key><string>automatic</string>
|
||||
<key>uploadSymbols</key><true/>
|
||||
<key>stripSwiftSymbols</key><true/>
|
||||
<key>destination</key><string>export</string>
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST
|
||||
ok "wrote ui/ios/ExportOptions.plist (method=$EXPORT_METHOD)"
|
||||
|
||||
# Resolve pods now so any CocoaPods error surfaces before the long archive.
|
||||
if command -v pod >/dev/null; then
|
||||
( cd "$IOSDIR" && pod install )
|
||||
ok "pod install complete"
|
||||
else
|
||||
ok "CocoaPods 'pod' not on PATH — flutter will run it during the build (sudo gem install cocoapods to install)"
|
||||
fi
|
||||
|
||||
# ---- 6. flutter build ipa --------------------------------------------------
|
||||
say "6/6 flutter build ipa"
|
||||
( cd "$UI" && "$FLUTTER" build ipa --release --export-options-plist=ios/ExportOptions.plist )
|
||||
|
||||
IPA="$(ls -t "$UI/build/ios/ipa/"*.ipa 2>/dev/null | head -1 || true)"
|
||||
echo ""
|
||||
echo "=== DONE ==="
|
||||
echo "xcframework : $DIST/$XCFRAMEWORK (also staged in ui/ios/)"
|
||||
if [ -n "$IPA" ]; then
|
||||
echo "ipa : $IPA"
|
||||
echo ""
|
||||
echo "Upload to App Store Connect / TestFlight with:"
|
||||
echo " xcrun altool --upload-app -f \"$IPA\" -t ios --apiKey <KEY_ID> --apiIssuer <ISSUER_ID>"
|
||||
echo "or open ui/build/ios/archive/Runner.xcarchive in Xcode > Organizer > Distribute App."
|
||||
else
|
||||
echo "ipa : (not found under ui/build/ios/ipa — check the flutter output above)"
|
||||
fi
|
||||
@@ -0,0 +1,341 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# NetDesk by Akronlogic -- LINUX build + packaging script.
|
||||
#
|
||||
# Builds the release viewer (cargo build --release -p netdesk-viewer) for the
|
||||
# x86_64-unknown-linux-gnu target, then bundles TWO Linux distributions:
|
||||
#
|
||||
# * AppImage (NetDesk-<ver>-x86_64.AppImage) -- single-file, run anywhere,
|
||||
# libav* bundled via linuxdeploy; no install needed.
|
||||
# * .deb (netdesk_<ver>_amd64.deb) -- dpkg package with a
|
||||
# .desktop launcher + hicolor icons; apt/dpkg install.
|
||||
#
|
||||
# The Linux decode path (core/src/decode_vaapi.rs) hardware-decodes H.264 with
|
||||
# VAAPI through FFmpeg/libavcodec, so BUILDING needs the system FFmpeg + VAAPI
|
||||
# development libraries present (see docs/build-linux.md). At RUNTIME VAAPI is
|
||||
# optional -- the core falls back to the openh264 software decoder when no GPU
|
||||
# decoder is available.
|
||||
#
|
||||
# Outputs land in packaging/dist/linux/ .
|
||||
#
|
||||
# Usage:
|
||||
# export PATH="$HOME/.cargo/bin:$PATH"
|
||||
# packaging/build-linux.sh # build + AppImage + .deb
|
||||
# packaging/build-linux.sh --skip-build # reuse an existing binary
|
||||
# packaging/build-linux.sh --no-appimage # .deb only
|
||||
# packaging/build-linux.sh --no-deb # AppImage only
|
||||
# packaging/build-linux.sh --target <triple>
|
||||
#
|
||||
# (c) Akronlogic. All rights reserved.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# product / packaging identity
|
||||
# ---------------------------------------------------------------------------
|
||||
APP_NAME="NetDesk"
|
||||
BIN_NAME="netdesk-viewer" # cargo [[bin]] name
|
||||
APP_ID="com.akronlogic.netdesk" # == viewer ViewportBuilder::with_app_id
|
||||
PKG_NAME="netdesk" # dpkg package name
|
||||
TARGET="x86_64-unknown-linux-gnu"
|
||||
DEB_ARCH="amd64"
|
||||
MAINTAINER="Akronlogic <https://akronlogic.com>"
|
||||
HOMEPAGE="https://akronlogic.com"
|
||||
|
||||
SKIP_BUILD=0
|
||||
DO_APPIMAGE=1
|
||||
DO_DEB=1
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# tiny coloured logging helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
say() { printf '\033[36m%s\033[0m\n' "$*"; } # cyan (headline)
|
||||
step() { printf '\033[33m%s\033[0m\n' "$*"; } # yellow(step)
|
||||
ok() { printf '\033[32m%s\033[0m\n' "$*"; } # green (success)
|
||||
warn() { printf '\033[33m%s\033[0m\n' "WARN: $*" >&2; }
|
||||
die() { printf '\033[31m%s\033[0m\n' "ERROR: $*" >&2; exit 1; }
|
||||
|
||||
usage() {
|
||||
sed -n '2,40p' "$0" | sed 's/^#\s\{0,1\}//'
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# args
|
||||
# ---------------------------------------------------------------------------
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--skip-build) SKIP_BUILD=1 ;;
|
||||
--no-appimage) DO_APPIMAGE=0 ;;
|
||||
--no-deb) DO_DEB=0 ;;
|
||||
--target) shift; [ $# -gt 0 ] || die "--target needs a value"; TARGET="$1" ;;
|
||||
--target=*) TARGET="${1#*=}" ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) die "unknown argument: $1 (try --help)" ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# paths
|
||||
# ---------------------------------------------------------------------------
|
||||
PKG_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$PKG_DIR")"
|
||||
ASSETS_DIR="$ROOT_DIR/assets"
|
||||
DIST_DIR="$PKG_DIR/dist/linux"
|
||||
TOOLS_DIR="$PKG_DIR/dist/.tools"
|
||||
|
||||
VERSION="$(grep -m1 '^version' "$ROOT_DIR/Cargo.toml" | sed -E 's/.*"([^"]+)".*/\1/')"
|
||||
[ -n "$VERSION" ] || VERSION="0.0.0"
|
||||
|
||||
BIN="$ROOT_DIR/target/$TARGET/release/$BIN_NAME"
|
||||
DESKTOP_SRC="$DIST_DIR/$APP_ID.desktop"
|
||||
|
||||
say "=== NetDesk Linux build (Akronlogic) ==="
|
||||
echo " root : $ROOT_DIR"
|
||||
echo " version : $VERSION"
|
||||
echo " target : $TARGET"
|
||||
echo " dist : $DIST_DIR"
|
||||
echo " outputs : $([ $DO_APPIMAGE -eq 1 ] && echo -n 'AppImage ')$([ $DO_DEB -eq 1 ] && echo -n '.deb')"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# preflight
|
||||
# ---------------------------------------------------------------------------
|
||||
[ "$(uname -s)" = "Linux" ] || die "this script must run on Linux (building the x86_64-unknown-linux-gnu viewer)."
|
||||
|
||||
# make sure a rustup/cargo install under ~/.cargo is reachable
|
||||
case ":$PATH:" in *":$HOME/.cargo/bin:"*) : ;; *) PATH="$HOME/.cargo/bin:$PATH" ;; esac
|
||||
export PATH
|
||||
|
||||
if [ "$SKIP_BUILD" -eq 0 ]; then
|
||||
command -v cargo >/dev/null 2>&1 || die "cargo not found on PATH. Install Rust (https://rustup.rs) and/or 'export PATH=\$HOME/.cargo/bin:\$PATH'."
|
||||
|
||||
# The decode_vaapi.rs backend links FFmpeg via the ffmpeg-next crate; its
|
||||
# build script (ffmpeg-sys-next) locates libav* with pkg-config and needs
|
||||
# libclang (bindgen). Fail fast with a copy-pasteable install hint.
|
||||
if ! command -v pkg-config >/dev/null 2>&1 || ! pkg-config --exists libavcodec 2>/dev/null; then
|
||||
warn "FFmpeg development libraries (libavcodec) were not found via pkg-config."
|
||||
echo " Debian/Ubuntu:"
|
||||
echo " sudo apt install -y build-essential clang pkg-config nasm \\"
|
||||
echo " libavcodec-dev libavformat-dev libavutil-dev libswscale-dev \\"
|
||||
echo " libavfilter-dev libavdevice-dev libswresample-dev \\"
|
||||
echo " libva-dev libgl1-mesa-dev"
|
||||
echo " Fedora:"
|
||||
echo " sudo dnf install -y gcc gcc-c++ clang pkgconf-pkg-config nasm \\"
|
||||
echo " ffmpeg-devel libva-devel mesa-libGL-devel"
|
||||
die "install the build dependencies above, then re-run (see docs/build-linux.md)."
|
||||
fi
|
||||
command -v clang >/dev/null 2>&1 || warn "clang/libclang not found -- ffmpeg-sys-next bindgen will fail. Install 'clang'."
|
||||
fi
|
||||
|
||||
mkdir -p "$DIST_DIR" "$TOOLS_DIR"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. build the release viewer
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ "$SKIP_BUILD" -eq 0 ]; then
|
||||
step "[1] cargo build --release -p $BIN_NAME --target $TARGET"
|
||||
# add the target when rustup is in use; a no-op / harmless on a native host.
|
||||
command -v rustup >/dev/null 2>&1 && rustup target add "$TARGET" >/dev/null 2>&1 || true
|
||||
( cd "$ROOT_DIR" && cargo build --release -p "$BIN_NAME" --target "$TARGET" )
|
||||
else
|
||||
step "[1] --skip-build: reusing existing binary"
|
||||
fi
|
||||
[ -x "$BIN" ] || die "viewer binary not found: $BIN (remove --skip-build to build it)."
|
||||
echo " binary : $BIN ($(du -h "$BIN" | cut -f1))"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. shared .desktop launcher (used by BOTH the AppImage and the .deb)
|
||||
# ---------------------------------------------------------------------------
|
||||
# The .desktop filename and the Icon= key both use the app-id so GNOME/KDE
|
||||
# associate the running window (winit sets WM_CLASS / Wayland app_id from
|
||||
# with_app_id("com.akronlogic.netdesk")) with this launcher + its icon.
|
||||
step "[2] writing $APP_ID.desktop"
|
||||
cat > "$DESKTOP_SRC" <<'DESKTOP'
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Version=1.0
|
||||
Name=NetDesk
|
||||
GenericName=Remote Desktop
|
||||
Comment=Secure remote desktop viewer by Akronlogic
|
||||
Exec=netdesk-viewer
|
||||
TryExec=netdesk-viewer
|
||||
Icon=com.akronlogic.netdesk
|
||||
Terminal=false
|
||||
Categories=Network;RemoteAccess;
|
||||
Keywords=remote;desktop;screen;share;vnc;rdp;netdesk;
|
||||
StartupNotify=true
|
||||
StartupWMClass=com.akronlogic.netdesk
|
||||
DESKTOP
|
||||
|
||||
ARTIFACTS=()
|
||||
|
||||
# ===========================================================================
|
||||
# 3. .deb (built first -- needs no network and no FUSE)
|
||||
# ===========================================================================
|
||||
if [ "$DO_DEB" -eq 1 ]; then
|
||||
step "[3] building .deb"
|
||||
command -v dpkg-deb >/dev/null 2>&1 || die "dpkg-deb not found (install the 'dpkg' package) -- cannot build the .deb. Use --no-deb to skip."
|
||||
|
||||
DEB_ROOT="$DIST_DIR/deb"
|
||||
rm -rf "$DEB_ROOT"
|
||||
|
||||
# payload ----------------------------------------------------------------
|
||||
install -Dm755 "$BIN" "$DEB_ROOT/usr/bin/$BIN_NAME"
|
||||
ln -sf "$BIN_NAME" "$DEB_ROOT/usr/bin/netdesk" # convenience alias
|
||||
install -Dm644 "$DESKTOP_SRC" "$DEB_ROOT/usr/share/applications/$APP_ID.desktop"
|
||||
install -Dm644 "$ASSETS_DIR/icon-512.png" "$DEB_ROOT/usr/share/icons/hicolor/512x512/apps/$APP_ID.png"
|
||||
install -Dm644 "$ASSETS_DIR/icon.png" "$DEB_ROOT/usr/share/icons/hicolor/256x256/apps/$APP_ID.png"
|
||||
install -Dm644 "$ASSETS_DIR/logo.svg" "$DEB_ROOT/usr/share/icons/hicolor/scalable/apps/$APP_ID.svg"
|
||||
|
||||
# copyright --------------------------------------------------------------
|
||||
install -d "$DEB_ROOT/usr/share/doc/$PKG_NAME"
|
||||
cat > "$DEB_ROOT/usr/share/doc/$PKG_NAME/copyright" <<'COPYRIGHT'
|
||||
NetDesk -- native remote desktop viewer.
|
||||
Developed by Akronlogic (https://akronlogic.com).
|
||||
|
||||
Copyright (c) Akronlogic. All rights reserved.
|
||||
License: Proprietary. Full IP ownership, no AGPL.
|
||||
COPYRIGHT
|
||||
|
||||
# Depends -----------------------------------------------------------------
|
||||
# Curated runtime dependency set. NOTE: libGL, libva, libX11/xcb, Wayland and
|
||||
# libxkbcommon are dlopen()ed at runtime (by glutin/winit and by libavcodec's
|
||||
# VAAPI backend), so ldd/dpkg-shlibdeps would MISS them -- they are listed
|
||||
# explicitly here. FFmpeg sonames differ per release, so each libav* uses
|
||||
# alternatives ending in the generic 'ffmpeg' package as a catch-all.
|
||||
BASE_DEPS="libc6, libgcc-s1, libstdc++6"
|
||||
FFMPEG_DEPS="libavcodec60 | libavcodec59 | libavcodec58 | ffmpeg, libavutil58 | libavutil57 | libavutil56 | ffmpeg, libswscale7 | libswscale6 | libswscale5 | ffmpeg"
|
||||
DLOPEN_DEPS="libgl1, libva2, libva-drm2, libva-x11-2, libxkbcommon0, libx11-6, libxcb1"
|
||||
DEPENDS="$BASE_DEPS, $FFMPEG_DEPS, $DLOPEN_DEPS"
|
||||
|
||||
INSTALLED_KB="$(du -sk "$DEB_ROOT" | cut -f1)"
|
||||
|
||||
install -d "$DEB_ROOT/DEBIAN"
|
||||
cat > "$DEB_ROOT/DEBIAN/control" <<CONTROL
|
||||
Package: $PKG_NAME
|
||||
Version: $VERSION
|
||||
Section: net
|
||||
Priority: optional
|
||||
Architecture: $DEB_ARCH
|
||||
Maintainer: $MAINTAINER
|
||||
Installed-Size: $INSTALLED_KB
|
||||
Depends: $DEPENDS
|
||||
Recommends: intel-media-va-driver | i965-va-driver | mesa-va-drivers
|
||||
Suggests: vainfo
|
||||
Homepage: $HOMEPAGE
|
||||
Description: NetDesk remote desktop viewer by Akronlogic
|
||||
NetDesk is a secure, self-hosted remote desktop client. The native viewer
|
||||
renders the remote screen with GPU-filtered scaling over a TeamViewer/
|
||||
RustDesk-style toolbar (eframe + egui).
|
||||
.
|
||||
H.264 video is hardware-decoded with VAAPI through FFmpeg/libavcodec when a
|
||||
GPU decoder is available, and falls back to an openh264 software decoder
|
||||
otherwise, so it also works on headless / GPU-less machines.
|
||||
CONTROL
|
||||
|
||||
# maintainer scripts: refresh the desktop + icon caches ------------------
|
||||
cat > "$DEB_ROOT/DEBIAN/postinst" <<'POSTINST'
|
||||
#!/bin/sh
|
||||
set -e
|
||||
if command -v update-desktop-database >/dev/null 2>&1; then
|
||||
update-desktop-database -q /usr/share/applications 2>/dev/null || true
|
||||
fi
|
||||
if command -v gtk-update-icon-cache >/dev/null 2>&1; then
|
||||
gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor 2>/dev/null || true
|
||||
fi
|
||||
exit 0
|
||||
POSTINST
|
||||
|
||||
cat > "$DEB_ROOT/DEBIAN/postrm" <<'POSTRM'
|
||||
#!/bin/sh
|
||||
set -e
|
||||
if command -v update-desktop-database >/dev/null 2>&1; then
|
||||
update-desktop-database -q /usr/share/applications 2>/dev/null || true
|
||||
fi
|
||||
if command -v gtk-update-icon-cache >/dev/null 2>&1; then
|
||||
gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor 2>/dev/null || true
|
||||
fi
|
||||
exit 0
|
||||
POSTRM
|
||||
chmod 0755 "$DEB_ROOT/DEBIAN/postinst" "$DEB_ROOT/DEBIAN/postrm"
|
||||
|
||||
OUT_DEB="$DIST_DIR/${PKG_NAME}_${VERSION}_${DEB_ARCH}.deb"
|
||||
rm -f "$OUT_DEB"
|
||||
# --root-owner-group => root:root ownership with no fakeroot (dpkg >= 1.19).
|
||||
if ! dpkg-deb --build --root-owner-group "$DEB_ROOT" "$OUT_DEB" 2>/dev/null; then
|
||||
warn "dpkg-deb --root-owner-group unavailable; building without it (files owned by the current uid)."
|
||||
dpkg-deb --build "$DEB_ROOT" "$OUT_DEB"
|
||||
fi
|
||||
[ -f "$OUT_DEB" ] || die "dpkg-deb reported success but $OUT_DEB is missing."
|
||||
ARTIFACTS+=("$OUT_DEB")
|
||||
ok " .deb : $OUT_DEB ($(du -h "$OUT_DEB" | cut -f1))"
|
||||
fi
|
||||
|
||||
# ===========================================================================
|
||||
# 4. AppImage (via linuxdeploy; bundles libav* etc.)
|
||||
# ===========================================================================
|
||||
if [ "$DO_APPIMAGE" -eq 1 ]; then
|
||||
step "[4] building AppImage"
|
||||
|
||||
# 4a. obtain linuxdeploy (cache under dist/.tools, or set LINUXDEPLOY=/path)
|
||||
LINUXDEPLOY="${LINUXDEPLOY:-$TOOLS_DIR/linuxdeploy-x86_64.AppImage}"
|
||||
if [ ! -x "$LINUXDEPLOY" ]; then
|
||||
url="https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
|
||||
echo " fetching linuxdeploy -> $LINUXDEPLOY"
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fL --retry 3 -o "$LINUXDEPLOY" "$url" || die "failed to download linuxdeploy (set LINUXDEPLOY=/path to a local copy, or --no-appimage)."
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -q -O "$LINUXDEPLOY" "$url" || die "failed to download linuxdeploy (set LINUXDEPLOY=/path to a local copy, or --no-appimage)."
|
||||
else
|
||||
die "need curl or wget to download linuxdeploy. Install one, set LINUXDEPLOY=/path, or pass --no-appimage."
|
||||
fi
|
||||
chmod +x "$LINUXDEPLOY"
|
||||
fi
|
||||
# run AppImage tools without FUSE (CI / containers / minimal hosts).
|
||||
export APPIMAGE_EXTRACT_AND_RUN=1
|
||||
|
||||
# 4b. assemble the AppDir
|
||||
APPDIR="$DIST_DIR/AppDir"
|
||||
rm -rf "$APPDIR"
|
||||
install -Dm755 "$BIN" "$APPDIR/usr/bin/$BIN_NAME"
|
||||
install -Dm644 "$DESKTOP_SRC" "$APPDIR/usr/share/applications/$APP_ID.desktop"
|
||||
install -Dm644 "$ASSETS_DIR/icon-512.png" "$APPDIR/usr/share/icons/hicolor/512x512/apps/$APP_ID.png"
|
||||
install -Dm644 "$ASSETS_DIR/icon.png" "$APPDIR/usr/share/icons/hicolor/256x256/apps/$APP_ID.png"
|
||||
install -Dm644 "$ASSETS_DIR/logo.svg" "$APPDIR/usr/share/icons/hicolor/scalable/apps/$APP_ID.svg"
|
||||
# top-level icon + .DirIcon + desktop that appimagetool expects at the root
|
||||
cp "$ASSETS_DIR/icon-512.png" "$APPDIR/$APP_ID.png"
|
||||
cp "$DESKTOP_SRC" "$APPDIR/$APP_ID.desktop"
|
||||
( cd "$APPDIR" && ln -sf "$APP_ID.png" .DirIcon )
|
||||
|
||||
# 4c. run linuxdeploy: it ldd-scans usr/bin, copies the app's shared libs
|
||||
# (libav*, etc.) into the AppDir, writes AppRun, and emits the AppImage.
|
||||
# libGL / libX11 / Wayland / libva are intentionally NOT bundled (they are
|
||||
# dlopen()ed and must come from the host graphics stack).
|
||||
OUT_APPIMAGE="$DIST_DIR/${APP_NAME}-${VERSION}-x86_64.AppImage"
|
||||
rm -f "$OUT_APPIMAGE"
|
||||
if ( cd "$DIST_DIR" && OUTPUT="$(basename "$OUT_APPIMAGE")" VERSION="$VERSION" \
|
||||
"$LINUXDEPLOY" \
|
||||
--appdir "$APPDIR" \
|
||||
-e "$APPDIR/usr/bin/$BIN_NAME" \
|
||||
-d "$APPDIR/usr/share/applications/$APP_ID.desktop" \
|
||||
-i "$APPDIR/usr/share/icons/hicolor/512x512/apps/$APP_ID.png" \
|
||||
--icon-filename "$APP_ID" \
|
||||
--output appimage ) && [ -f "$OUT_APPIMAGE" ]; then
|
||||
chmod +x "$OUT_APPIMAGE"
|
||||
ARTIFACTS+=("$OUT_APPIMAGE")
|
||||
ok " AppImage: $OUT_APPIMAGE ($(du -h "$OUT_APPIMAGE" | cut -f1))"
|
||||
else
|
||||
warn "linuxdeploy did not produce an AppImage (network / FUSE / tool error). The .deb above is unaffected."
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# summary
|
||||
# ---------------------------------------------------------------------------
|
||||
echo
|
||||
if [ "${#ARTIFACTS[@]}" -gt 0 ]; then
|
||||
ok "Linux build complete. Artifacts:"
|
||||
for a in "${ARTIFACTS[@]}"; do echo " $a ($(du -h "$a" | cut -f1))"; done
|
||||
else
|
||||
die "no artifacts were produced."
|
||||
fi
|
||||
@@ -0,0 +1,364 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# NetDesk by Akronlogic -- macOS DESKTOP build script.
|
||||
#
|
||||
# Builds the SAME egui viewer/host-panel binary the Windows editions ship
|
||||
# (crate `netdesk-viewer`) for macOS, as a signed + notarized universal app:
|
||||
#
|
||||
# 1. rustup: add the aarch64-apple-darwin + x86_64-apple-darwin std targets.
|
||||
# 2. cargo build --release -p netdesk-viewer for BOTH targets.
|
||||
# 3. lipo the two binaries into one universal (arm64 + x86_64) executable.
|
||||
# 4. iconutil: turn assets/icon*.png into NetDesk.icns.
|
||||
# 5. Assemble NetDesk.app (Contents/MacOS + Info.plist + Resources/.icns).
|
||||
# 6. codesign with a "Developer ID Application" identity (hardened runtime).
|
||||
# 7. notarize the .app via `xcrun notarytool submit --wait`, then staple it.
|
||||
# 8. Build a .dmg from the stapled .app, sign + notarize + staple the .dmg.
|
||||
#
|
||||
# On macOS the H.264 stream is decoded on the Apple video engine through the
|
||||
# VideoToolbox path (core/src/decode_vt.rs, VTDecompressionSession); it falls
|
||||
# back to the portable openh264 software decoder if the hardware path can't
|
||||
# initialize. Nothing here is Mac-specific beyond packaging -- the viewer code
|
||||
# is identical to Windows. See docs/build-macos.md.
|
||||
#
|
||||
# THIS SCRIPT RUNS ON macOS (the user's M4 Mac), NOT on the Windows dev box.
|
||||
# All outputs land in packaging/dist/.
|
||||
#
|
||||
# Usage (from the repo root or anywhere):
|
||||
# bash packaging/build-macos.sh --notary-profile netdesk
|
||||
# bash packaging/build-macos.sh --skip-build # reuse existing targets
|
||||
# bash packaging/build-macos.sh --no-sign # unsigned .app + .dmg
|
||||
# bash packaging/build-macos.sh --no-notarize # sign but don't notarize
|
||||
# bash packaging/build-macos.sh --identity "Developer ID Application: Akronlogic (TEAMID)" \
|
||||
# --apple-id you@akronlogic.com --team-id TEAMID
|
||||
#
|
||||
# Notarization credentials (pick ONE):
|
||||
# --notary-profile <name> a keychain profile created once with:
|
||||
# xcrun notarytool store-credentials <name> --apple-id <e> --team-id <t> --password <app-pw>
|
||||
# --apple-id <email> --team-id <TEAMID> + env NETDESK_NOTARY_PASSWORD=<app-specific-password>
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# defaults / args
|
||||
# ---------------------------------------------------------------------------
|
||||
BUNDLE_ID="com.akronlogic.netdesk"
|
||||
APP_NAME="NetDesk"
|
||||
MIN_MACOS="11.0" # Big Sur: oldest OS the universal app targets
|
||||
CATEGORY="public.app-category.utilities"
|
||||
COPYRIGHT="© Akronlogic. All rights reserved."
|
||||
|
||||
IDENTITY="" # "Developer ID Application: ... (TEAMID)" (empty = auto-detect)
|
||||
APPLE_ID=""
|
||||
TEAM_ID=""
|
||||
NOTARY_PROFILE=""
|
||||
ENTITLEMENTS="" # optional path to an entitlements .plist
|
||||
ICON_SRC="" # optional path to a master PNG (>= 512px, ideally 1024)
|
||||
VERSION="" # override the Cargo workspace version
|
||||
|
||||
SKIP_BUILD=0
|
||||
NO_SIGN=0
|
||||
NO_NOTARIZE=0
|
||||
STAPLE_APP=1 # also notarize+staple the standalone .app (not just the .dmg)
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--identity) IDENTITY="$2"; shift 2 ;;
|
||||
--apple-id) APPLE_ID="$2"; shift 2 ;;
|
||||
--team-id) TEAM_ID="$2"; shift 2 ;;
|
||||
--notary-profile) NOTARY_PROFILE="$2"; shift 2 ;;
|
||||
--entitlements) ENTITLEMENTS="$2"; shift 2 ;;
|
||||
--icon) ICON_SRC="$2"; shift 2 ;;
|
||||
--version) VERSION="$2"; shift 2 ;;
|
||||
--skip-build) SKIP_BUILD=1; shift ;;
|
||||
--no-sign) NO_SIGN=1; shift ;;
|
||||
--no-notarize) NO_NOTARIZE=1; shift ;;
|
||||
--no-staple-app) STAPLE_APP=0; shift ;;
|
||||
-h|--help)
|
||||
sed -n '2,46p' "$0"; exit 0 ;;
|
||||
*) echo "unknown option: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# pretty output
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ -t 1 ]; then
|
||||
C_CYAN='\033[36m'; C_YEL='\033[33m'; C_GRN='\033[32m'; C_RED='\033[31m'; C_DIM='\033[2m'; C_OFF='\033[0m'
|
||||
else
|
||||
C_CYAN=''; C_YEL=''; C_GRN=''; C_RED=''; C_DIM=''; C_OFF=''
|
||||
fi
|
||||
say() { printf "%b%s%b\n" "$C_CYAN" "$*" "$C_OFF"; }
|
||||
step() { printf "\n%b%s%b\n" "$C_YEL" "$*" "$C_OFF"; }
|
||||
ok() { printf "%b%s%b\n" "$C_GRN" "$*" "$C_OFF"; }
|
||||
dim() { printf "%b %s%b\n" "$C_DIM" "$*" "$C_OFF"; }
|
||||
warn() { printf "%b%s%b\n" "$C_RED" "$*" "$C_OFF"; }
|
||||
die() { printf "%b%s%b\n" "$C_RED" "$*" "$C_OFF" >&2; exit 1; }
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# paths
|
||||
# ---------------------------------------------------------------------------
|
||||
PKG_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT_DIR="$(cd "$PKG_DIR/.." && pwd)"
|
||||
DIST_DIR="$PKG_DIR/dist"
|
||||
ASSETS_DIR="$ROOT_DIR/assets"
|
||||
|
||||
APP_DIR="$DIST_DIR/$APP_NAME.app"
|
||||
MACOS_DIR="$APP_DIR/Contents/MacOS"
|
||||
RES_DIR="$APP_DIR/Contents/Resources"
|
||||
PLIST="$APP_DIR/Contents/Info.plist"
|
||||
EXE_OUT="$MACOS_DIR/$APP_NAME"
|
||||
|
||||
TARGET_ARM="aarch64-apple-darwin"
|
||||
TARGET_X64="x86_64-apple-darwin"
|
||||
BIN_ARM="$ROOT_DIR/target/$TARGET_ARM/release/netdesk-viewer"
|
||||
BIN_X64="$ROOT_DIR/target/$TARGET_X64/release/netdesk-viewer"
|
||||
|
||||
DMG_STAGE="$DIST_DIR/dmg-stage"
|
||||
DMG_OUT="$DIST_DIR/$APP_NAME-macOS-universal.dmg"
|
||||
APP_ZIP="$DIST_DIR/$APP_NAME-macOS-universal.zip"
|
||||
|
||||
say "=== NetDesk macOS build (Akronlogic) ==="
|
||||
dim "root : $ROOT_DIR"
|
||||
dim "dist : $DIST_DIR"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# preflight
|
||||
# ---------------------------------------------------------------------------
|
||||
[ "$(uname -s)" = "Darwin" ] || die "This script must run on macOS (uname is '$(uname -s)'). Build on the Mac, not the Windows box."
|
||||
|
||||
need() { command -v "$1" >/dev/null 2>&1 || die "required tool not found on PATH: $1"; }
|
||||
need cargo
|
||||
need rustup
|
||||
need lipo
|
||||
need iconutil
|
||||
need sips
|
||||
need hdiutil
|
||||
need codesign
|
||||
need xcrun
|
||||
|
||||
xcode-select -p >/dev/null 2>&1 || die "Xcode command-line tools missing. Run: xcode-select --install"
|
||||
|
||||
# version from the Cargo workspace unless overridden
|
||||
if [ -z "$VERSION" ]; then
|
||||
VERSION="$(awk -F'"' '/^\[workspace.package\]/{f=1} f&&/^version[[:space:]]*=/{print $2; exit}' "$ROOT_DIR/Cargo.toml")"
|
||||
[ -n "$VERSION" ] || VERSION="0.0.0"
|
||||
fi
|
||||
dim "version : $VERSION"
|
||||
dim "bundle : $BUNDLE_ID"
|
||||
|
||||
mkdir -p "$DIST_DIR"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1 + 2. rust targets + release builds (both arches)
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ "$SKIP_BUILD" -eq 0 ]; then
|
||||
step "[1/8] rustup target add ($TARGET_ARM, $TARGET_X64)"
|
||||
rustup target add "$TARGET_ARM" "$TARGET_X64"
|
||||
|
||||
step "[2/8] cargo build --release -p netdesk-viewer (both arches)"
|
||||
( cd "$ROOT_DIR" && cargo build --release -p netdesk-viewer --target "$TARGET_ARM" )
|
||||
( cd "$ROOT_DIR" && cargo build --release -p netdesk-viewer --target "$TARGET_X64" )
|
||||
else
|
||||
step "[1-2/8] --skip-build: reusing existing per-target binaries"
|
||||
fi
|
||||
[ -f "$BIN_ARM" ] || die "missing arm64 binary: $BIN_ARM"
|
||||
[ -f "$BIN_X64" ] || die "missing x86_64 binary: $BIN_X64"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. lipo -> universal binary
|
||||
# ---------------------------------------------------------------------------
|
||||
step "[3/8] lipo -> universal (arm64 + x86_64)"
|
||||
rm -rf "$APP_DIR"
|
||||
mkdir -p "$MACOS_DIR" "$RES_DIR"
|
||||
lipo -create -output "$EXE_OUT" "$BIN_ARM" "$BIN_X64"
|
||||
chmod +x "$EXE_OUT"
|
||||
dim "$(lipo -info "$EXE_OUT")"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. icns via iconutil
|
||||
# ---------------------------------------------------------------------------
|
||||
step "[4/8] iconutil -> $APP_NAME.icns"
|
||||
# Prefer the largest master we have: an explicit --icon, else a 1024 PNG if it
|
||||
# exists, else the 512, else the 256 (assets/icon.png). iconutil needs a full
|
||||
# .iconset of fixed-named PNGs; sips resizes the master down into each slot.
|
||||
if [ -z "$ICON_SRC" ]; then
|
||||
for c in "$ASSETS_DIR/icon-1024.png" "$ASSETS_DIR/icon-512.png" "$ASSETS_DIR/icon.png"; do
|
||||
if [ -f "$c" ]; then ICON_SRC="$c"; break; fi
|
||||
done
|
||||
fi
|
||||
[ -n "$ICON_SRC" ] && [ -f "$ICON_SRC" ] || die "no icon PNG found (looked for assets/icon-1024.png, icon-512.png, icon.png; or pass --icon)"
|
||||
dim "icon master: $ICON_SRC"
|
||||
|
||||
ICONSET="$DIST_DIR/$APP_NAME.iconset"
|
||||
rm -rf "$ICONSET"; mkdir -p "$ICONSET"
|
||||
# name px
|
||||
gen() { sips -z "$2" "$2" "$ICON_SRC" --out "$ICONSET/$1" >/dev/null; }
|
||||
gen icon_16x16.png 16
|
||||
gen icon_16x16@2x.png 32
|
||||
gen icon_32x32.png 32
|
||||
gen icon_32x32@2x.png 64
|
||||
gen icon_128x128.png 128
|
||||
gen icon_128x128@2x.png 256
|
||||
gen icon_256x256.png 256
|
||||
gen icon_256x256@2x.png 512
|
||||
gen icon_512x512.png 512
|
||||
gen icon_512x512@2x.png 1024
|
||||
iconutil -c icns "$ICONSET" -o "$RES_DIR/$APP_NAME.icns"
|
||||
rm -rf "$ICONSET"
|
||||
dim "wrote $RES_DIR/$APP_NAME.icns"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5. Info.plist + PkgInfo (assemble the bundle)
|
||||
# ---------------------------------------------------------------------------
|
||||
step "[5/8] assemble $APP_NAME.app"
|
||||
cat > "$PLIST" <<PLIST_EOF
|
||||
<?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>$APP_NAME</string>
|
||||
<key>CFBundleDisplayName</key> <string>$APP_NAME</string>
|
||||
<key>CFBundleIdentifier</key> <string>$BUNDLE_ID</string>
|
||||
<key>CFBundleExecutable</key> <string>$APP_NAME</string>
|
||||
<key>CFBundleIconFile</key> <string>$APP_NAME</string>
|
||||
<key>CFBundleVersion</key> <string>$VERSION</string>
|
||||
<key>CFBundleShortVersionString</key> <string>$VERSION</string>
|
||||
<key>CFBundlePackageType</key> <string>APPL</string>
|
||||
<key>CFBundleSignature</key> <string>????</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key> <string>6.0</string>
|
||||
<key>LSMinimumSystemVersion</key> <string>$MIN_MACOS</string>
|
||||
<key>LSApplicationCategoryType</key> <string>$CATEGORY</string>
|
||||
<key>NSHighResolutionCapable</key> <true/>
|
||||
<key>NSHumanReadableCopyright</key> <string>$COPYRIGHT</string>
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key> <true/>
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST_EOF
|
||||
|
||||
printf 'APPL????' > "$APP_DIR/Contents/PkgInfo"
|
||||
ok "bundle staged: $APP_DIR"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# resolve signing identity
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ "$NO_SIGN" -eq 0 ] && [ -z "$IDENTITY" ]; then
|
||||
# auto-detect a single "Developer ID Application" identity from the keychain
|
||||
IDENTITY="$(security find-identity -v -p codesigning 2>/dev/null \
|
||||
| sed -n 's/.*"\(Developer ID Application: [^"]*\)".*/\1/p' | head -n1 || true)"
|
||||
fi
|
||||
|
||||
CAN_SIGN=0
|
||||
if [ "$NO_SIGN" -eq 0 ] && [ -n "$IDENTITY" ]; then CAN_SIGN=1; fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6. codesign (hardened runtime + secure timestamp)
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ "$CAN_SIGN" -eq 1 ]; then
|
||||
step "[6/8] codesign ($IDENTITY)"
|
||||
CS_ARGS="--force --timestamp --options runtime --sign $IDENTITY"
|
||||
if [ -n "$ENTITLEMENTS" ]; then
|
||||
[ -f "$ENTITLEMENTS" ] || die "entitlements file not found: $ENTITLEMENTS"
|
||||
dim "entitlements: $ENTITLEMENTS"
|
||||
fi
|
||||
# single-binary bundle (no nested frameworks/dylibs): sign the executable,
|
||||
# then the bundle. --options runtime is required for notarization.
|
||||
if [ -n "$ENTITLEMENTS" ]; then
|
||||
codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS" --sign "$IDENTITY" "$EXE_OUT"
|
||||
codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS" --sign "$IDENTITY" "$APP_DIR"
|
||||
else
|
||||
codesign --force --timestamp --options runtime --sign "$IDENTITY" "$EXE_OUT"
|
||||
codesign --force --timestamp --options runtime --sign "$IDENTITY" "$APP_DIR"
|
||||
fi
|
||||
codesign --verify --deep --strict --verbose=2 "$APP_DIR"
|
||||
dim "signed + verified"
|
||||
else
|
||||
step "[6/8] codesign -- SKIPPED"
|
||||
if [ "$NO_SIGN" -eq 1 ]; then
|
||||
warn " --no-sign set: the .app and .dmg are UNSIGNED (Gatekeeper will block them on other Macs)."
|
||||
else
|
||||
warn " No \"Developer ID Application\" identity found in the keychain -- producing an UNSIGNED bundle."
|
||||
warn " Install your Developer ID Application cert, or pass --identity \"Developer ID Application: NAME (TEAMID)\"."
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# notarization helper
|
||||
# ---------------------------------------------------------------------------
|
||||
notary_auth_ok=0
|
||||
notary_args=""
|
||||
if [ -n "$NOTARY_PROFILE" ]; then
|
||||
notary_args="--keychain-profile $NOTARY_PROFILE"; notary_auth_ok=1
|
||||
elif [ -n "$APPLE_ID" ] && [ -n "$TEAM_ID" ] && [ -n "${NETDESK_NOTARY_PASSWORD:-}" ]; then
|
||||
notary_args="--apple-id $APPLE_ID --team-id $TEAM_ID --password $NETDESK_NOTARY_PASSWORD"; notary_auth_ok=1
|
||||
fi
|
||||
|
||||
CAN_NOTARIZE=0
|
||||
if [ "$CAN_SIGN" -eq 1 ] && [ "$NO_NOTARIZE" -eq 0 ] && [ "$notary_auth_ok" -eq 1 ]; then CAN_NOTARIZE=1; fi
|
||||
|
||||
notarize() { # $1 = path to .zip/.dmg to submit
|
||||
# shellcheck disable=SC2086
|
||||
xcrun notarytool submit "$1" $notary_args --wait
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 7. notarize + staple the standalone .app (via a zip; stapling needs the app)
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ "$CAN_NOTARIZE" -eq 1 ] && [ "$STAPLE_APP" -eq 1 ]; then
|
||||
step "[7/8] notarize + staple $APP_NAME.app"
|
||||
rm -f "$APP_ZIP"
|
||||
/usr/bin/ditto -c -k --keepParent "$APP_DIR" "$APP_ZIP"
|
||||
notarize "$APP_ZIP"
|
||||
xcrun stapler staple "$APP_DIR"
|
||||
xcrun stapler validate "$APP_DIR"
|
||||
dim "stapled $APP_NAME.app"
|
||||
else
|
||||
step "[7/8] notarize + staple .app -- SKIPPED"
|
||||
if [ "$CAN_SIGN" -eq 0 ]; then
|
||||
dim "(unsigned bundle cannot be notarized)"
|
||||
elif [ "$NO_NOTARIZE" -eq 1 ]; then
|
||||
dim "(--no-notarize set)"
|
||||
elif [ "$notary_auth_ok" -eq 0 ]; then
|
||||
warn " No notarization credentials. Pass --notary-profile <name>, or"
|
||||
warn " --apple-id <e> --team-id <t> with env NETDESK_NOTARY_PASSWORD=<app-pw>."
|
||||
warn " Create a profile once with: xcrun notarytool store-credentials <name> --apple-id <e> --team-id <t> --password <app-pw>"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 8. build the .dmg, then sign + notarize + staple it
|
||||
# ---------------------------------------------------------------------------
|
||||
step "[8/8] build $APP_NAME-macOS-universal.dmg"
|
||||
rm -rf "$DMG_STAGE"; mkdir -p "$DMG_STAGE"
|
||||
/usr/bin/ditto "$APP_DIR" "$DMG_STAGE/$APP_NAME.app"
|
||||
ln -s /Applications "$DMG_STAGE/Applications"
|
||||
rm -f "$DMG_OUT"
|
||||
hdiutil create -volname "$APP_NAME" -srcfolder "$DMG_STAGE" -fs HFS+ -format UDZO -ov "$DMG_OUT" >/dev/null
|
||||
rm -rf "$DMG_STAGE"
|
||||
dim "wrote $DMG_OUT"
|
||||
|
||||
if [ "$CAN_SIGN" -eq 1 ]; then
|
||||
codesign --force --timestamp --sign "$IDENTITY" "$DMG_OUT"
|
||||
dim "signed the .dmg"
|
||||
fi
|
||||
if [ "$CAN_NOTARIZE" -eq 1 ]; then
|
||||
dim "notarizing the .dmg ..."
|
||||
notarize "$DMG_OUT"
|
||||
xcrun stapler staple "$DMG_OUT"
|
||||
xcrun stapler validate "$DMG_OUT"
|
||||
dim "stapled the .dmg"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# summary
|
||||
# ---------------------------------------------------------------------------
|
||||
printf "\n"
|
||||
ok "macOS build complete."
|
||||
dim "app : $APP_DIR"
|
||||
[ -f "$DMG_OUT" ] && dim "dmg : $DMG_OUT ($(du -h "$DMG_OUT" | cut -f1))"
|
||||
if [ "$CAN_SIGN" -eq 0 ]; then
|
||||
warn "NOTE: artifacts are UNSIGNED -- for distribution, re-run on a Mac with a Developer ID Application cert."
|
||||
elif [ "$CAN_NOTARIZE" -eq 0 ]; then
|
||||
warn "NOTE: signed but NOT notarized -- re-run with notarization credentials (see --help) before distributing."
|
||||
else
|
||||
ok "Signed, notarized, and stapled -- ready to distribute."
|
||||
fi
|
||||
@@ -0,0 +1,73 @@
|
||||
<#
|
||||
NetDesk by Akronlogic -- MSI build script (WiX v4).
|
||||
|
||||
Produces dist\NetDesk.msi for enterprise / unattended deployment (GPO / SCCM /
|
||||
Intune). The MSI installs the portable payload and runs `NetDesk.exe --config`
|
||||
to write config.json non-interactively from the deployment properties:
|
||||
|
||||
msiexec /i NetDesk.msi /qn SERVER="wss://relay.corp/ws" REGKEY="CORP1" `
|
||||
PASSWORD="Sup3rSecret!" HOSTID="123456789"
|
||||
|
||||
Prerequisite (one time) -- the WiX toolset, which needs the .NET SDK:
|
||||
winget install Microsoft.DotNet.SDK.8 # or any current .NET SDK
|
||||
dotnet tool install --global wix
|
||||
|
||||
NOTE: this box has only the .NET RUNTIME (no SDK), so WiX cannot be installed
|
||||
here and the MSI has not been built/verified in this environment. The
|
||||
unattended `--config` injection the MSI drives IS proven (viewer
|
||||
config_cli_tests + a live `NetDesk.exe --config` run). Run this on a build box
|
||||
that has WiX.
|
||||
|
||||
Usage:
|
||||
powershell -ExecutionPolicy Bypass -File build-msi.ps1
|
||||
powershell -ExecutionPolicy Bypass -File build-msi.ps1 -SkipBuild # reuse staged payload
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$MingwBin = "C:\mingw-netdesk\mingw64\bin",
|
||||
[switch]$SkipBuild,
|
||||
[switch]$NoSign,
|
||||
[string]$Thumbprint = "YOUR_CODE_SIGNING_CERT_THUMBPRINT"
|
||||
)
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$PkgDir = $PSScriptRoot
|
||||
$RootDir = Split-Path -Parent $PkgDir
|
||||
$DistDir = Join-Path $PkgDir "dist"
|
||||
$StageDir = Join-Path $DistDir "NetDesk-Portable"
|
||||
$Wxs = Join-Path $PkgDir "netdesk.wxs"
|
||||
$MsiOut = Join-Path $DistDir "NetDesk.msi"
|
||||
$SignPs1 = Join-Path $RootDir "certs\sign-release.ps1"
|
||||
|
||||
Write-Host "=== NetDesk MSI build (Akronlogic) ===" -ForegroundColor Cyan
|
||||
|
||||
# 1. stage the signed portable payload (NetDesk.exe + runtime DLLs).
|
||||
$portableArgs = @{ MingwBin = $MingwBin }
|
||||
if ($SkipBuild) { $portableArgs.SkipBuild = $true }
|
||||
& (Join-Path $PkgDir "build-portable.ps1") @portableArgs
|
||||
if (-not (Test-Path (Join-Path $StageDir "NetDesk.exe"))) { throw "payload missing: $StageDir" }
|
||||
|
||||
# 2. locate WiX.
|
||||
$wix = Get-Command wix -ErrorAction SilentlyContinue
|
||||
if (-not $wix) {
|
||||
throw "WiX toolset not found. Install it (needs the .NET SDK): dotnet tool install --global wix"
|
||||
}
|
||||
|
||||
# 3. build the MSI, passing the payload dir as the $(Payload) preprocessor var.
|
||||
New-Item -ItemType Directory -Force -Path $DistDir | Out-Null
|
||||
& $wix.Source build $Wxs -d "Payload=$StageDir" -arch x64 -o $MsiOut
|
||||
if ($LASTEXITCODE) { throw "wix build failed (exit $LASTEXITCODE)" }
|
||||
Write-Host " built: $MsiOut"
|
||||
|
||||
# 4. sign the MSI with the same cert as the portable / installer.
|
||||
if (-not $NoSign -and (Test-Path $SignPs1)) {
|
||||
$signtool = Get-Command signtool.exe -ErrorAction SilentlyContinue
|
||||
if ($signtool) {
|
||||
& $signtool.Source sign /sha1 $Thumbprint /fd SHA256 /tr "http://timestamp.sectigo.com" /td SHA256 "$MsiOut"
|
||||
if ($LASTEXITCODE) { Write-Host " signtool returned $LASTEXITCODE (continuing)" -ForegroundColor DarkYellow }
|
||||
else { Write-Host " signed: $MsiOut" -ForegroundColor Green }
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`nMSI build complete -> $MsiOut" -ForegroundColor Green
|
||||
Write-Host "Deploy: msiexec /i NetDesk.msi /qn SERVER=... REGKEY=... PASSWORD=... HOSTID=..."
|
||||
@@ -0,0 +1,129 @@
|
||||
<#
|
||||
NetDesk by Akronlogic -- PORTABLE build + staging script.
|
||||
|
||||
Builds the release viewer (cargo build --release -p netdesk-viewer) with the
|
||||
MinGW GNU toolchain on PATH, then assembles a self-contained portable folder
|
||||
(NetDesk.exe + NetDeskHost.exe host engine + the 3 MinGW runtime DLLs +
|
||||
README) and zips it. No install needed -- the user unzips it anywhere and
|
||||
runs NetDesk.exe from the folder. One app = full host + remote-control viewer.
|
||||
|
||||
Outputs land in packaging\dist\ :
|
||||
NetDesk-Portable\ (staging folder, also consumed by the installer)
|
||||
NetDesk-Portable-win64.zip (the portable artifact)
|
||||
|
||||
Usage:
|
||||
powershell -ExecutionPolicy Bypass -File build-portable.ps1
|
||||
powershell -ExecutionPolicy Bypass -File build-portable.ps1 -SkipBuild # reuse existing exe
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$MingwBin = "C:\mingw-netdesk\mingw64\bin",
|
||||
[string]$CargoBin = (Join-Path $env:USERPROFILE ".cargo\bin"),
|
||||
[switch]$SkipBuild
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# ---- paths -----------------------------------------------------------------
|
||||
$PkgDir = $PSScriptRoot
|
||||
$RootDir = Split-Path -Parent $PkgDir
|
||||
$DistDir = Join-Path $PkgDir "dist"
|
||||
$StageDir = Join-Path $DistDir "NetDesk-Portable"
|
||||
$ZipPath = Join-Path $DistDir "NetDesk-Portable-win64.zip"
|
||||
$ExeSrc = Join-Path $RootDir "target\x86_64-pc-windows-gnu\release\netdesk-viewer.exe"
|
||||
|
||||
$Dlls = @("libstdc++-6.dll", "libgcc_s_seh-1.dll", "libwinpthread-1.dll")
|
||||
|
||||
Write-Host "=== NetDesk portable build (Akronlogic) ===" -ForegroundColor Cyan
|
||||
Write-Host "root : $RootDir"
|
||||
Write-Host "dist : $DistDir"
|
||||
|
||||
# ---- 1. build --------------------------------------------------------------
|
||||
if (-not $SkipBuild) {
|
||||
if (-not (Test-Path (Join-Path $MingwBin "gcc.exe"))) {
|
||||
throw "MinGW toolchain not found at '$MingwBin' (no gcc.exe). Pass -MingwBin <path> or install it."
|
||||
}
|
||||
$env:PATH = "$MingwBin;$CargoBin;$env:PATH"
|
||||
Write-Host "`n[1/4] cargo build --release -p netdesk-viewer" -ForegroundColor Yellow
|
||||
Push-Location $RootDir
|
||||
try {
|
||||
& cargo build --release -p netdesk-viewer --target x86_64-pc-windows-gnu
|
||||
if ($LASTEXITCODE -ne 0) { throw "cargo build failed (exit $LASTEXITCODE)" }
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
} else {
|
||||
Write-Host "`n[1/4] -SkipBuild set: reusing existing binary" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
if (-not (Test-Path $ExeSrc)) { throw "viewer binary not found: $ExeSrc" }
|
||||
|
||||
# ---- 2. assemble staging folder -------------------------------------------
|
||||
Write-Host "[2/4] assembling portable folder: $StageDir" -ForegroundColor Yellow
|
||||
if (Test-Path $StageDir) { Remove-Item $StageDir -Recurse -Force }
|
||||
New-Item -ItemType Directory -Path $StageDir -Force | Out-Null
|
||||
|
||||
# viewer renamed to the product name
|
||||
Copy-Item $ExeSrc (Join-Path $StageDir "NetDesk.exe") -Force
|
||||
|
||||
# the 3 MinGW runtime DLLs beside the exe
|
||||
foreach ($d in $Dlls) {
|
||||
$src = Join-Path $MingwBin $d
|
||||
if (-not (Test-Path $src)) { throw "missing runtime DLL: $src" }
|
||||
Copy-Item $src (Join-Path $StageDir $d) -Force
|
||||
}
|
||||
|
||||
# NOTE: NetDesk is now ONE pure-Rust binary. Start Service runs the native host
|
||||
# engine (DXGI capture + hardware H.264 + SendInput) IN-PROCESS, so there is no
|
||||
# separate NetDeskHost.exe to bundle any more — NetDesk.exe + the 3 runtime DLLs
|
||||
# are the whole app (host panel + remote-control viewer).
|
||||
|
||||
# ---- 3. README.txt ---------------------------------------------------------
|
||||
# Built from an array of lines (no here-string: Windows PowerShell 5.1
|
||||
# mis-parses here-strings in LF-only script files). Joined with CRLF and
|
||||
# written BOM-less so Notepad and every editor render it cleanly.
|
||||
Write-Host "[3/4] writing README.txt" -ForegroundColor Yellow
|
||||
$readmeLines = @(
|
||||
'NetDesk -- Portable Edition (Full Client)',
|
||||
'Developed by Akronlogic (https://akronlogic.com)',
|
||||
'',
|
||||
'NetDesk is ONE unified app. It is both the HOST (let someone control this PC)',
|
||||
'and the remote-control VIEWER (control another PC) -- a full client, like',
|
||||
'TeamViewer. Open it, and the home screen is your host panel; enter a Partner',
|
||||
'ID to connect out and it becomes the viewer.',
|
||||
'',
|
||||
'HOW TO RUN',
|
||||
' 1. Unzip this folder anywhere you like (Desktop, a USB stick, etc.).',
|
||||
' 2. Double-click NetDesk.exe.',
|
||||
' There is nothing to install and no admin rights are required. Keep every',
|
||||
' file in this folder together -- NetDesk.exe needs the three *.dll files',
|
||||
' beside it to run.',
|
||||
'',
|
||||
'WHAT''S IN THIS FOLDER',
|
||||
' NetDesk.exe The NetDesk full client (host engine + viewer, all in one).',
|
||||
' libstdc++-6.dll MinGW C++ runtime (required).',
|
||||
' libgcc_s_seh-1.dll MinGW GCC runtime (required).',
|
||||
' libwinpthread-1.dll MinGW threading runtime (required).',
|
||||
' README.txt This file.',
|
||||
'',
|
||||
'NOTES',
|
||||
' * Connects to the NetDesk server (netdesk.space) using the existing protocol.',
|
||||
' * To remove NetDesk, just delete this folder. Nothing is written elsewhere.',
|
||||
' * Prefer a Start Menu entry / Desktop shortcut + uninstaller? Use the',
|
||||
' installer edition (NetDesk-Setup-win64.exe) instead.',
|
||||
'',
|
||||
'(c) Akronlogic. All rights reserved.'
|
||||
)
|
||||
$readme = ($readmeLines -join "`r`n") + "`r`n"
|
||||
[System.IO.File]::WriteAllText((Join-Path $StageDir "README.txt"), $readme, (New-Object System.Text.UTF8Encoding($false)))
|
||||
|
||||
# ---- 4. zip ----------------------------------------------------------------
|
||||
Write-Host "[4/4] zipping -> $ZipPath" -ForegroundColor Yellow
|
||||
if (Test-Path $ZipPath) { Remove-Item $ZipPath -Force }
|
||||
Compress-Archive -Path (Join-Path $StageDir "*") -DestinationPath $ZipPath -CompressionLevel Optimal
|
||||
|
||||
$zi = Get-Item $ZipPath
|
||||
Write-Host "`nPortable build complete." -ForegroundColor Green
|
||||
Write-Host (" zip : {0}" -f $zi.FullName)
|
||||
Write-Host (" size : {0:N0} bytes ({1:N2} MB)" -f $zi.Length, ($zi.Length/1MB))
|
||||
Write-Host (" stage : {0}" -f $StageDir)
|
||||
@@ -0,0 +1,60 @@
|
||||
# NetDesk by Akronlogic — iOS Podfile TEMPLATE.
|
||||
#
|
||||
# packaging/build-ios.sh installs this as ui/ios/Podfile when `flutter create`
|
||||
# has not already produced one. It is the stock Flutter iOS Podfile with two
|
||||
# NetDesk changes: the platform min is uncommented (13.0, to match
|
||||
# NetDeskCore.xcframework) and the netdesk_core pod is linked into Runner.
|
||||
#
|
||||
# If a Podfile already exists, build-ios.sh leaves it in place and only injects
|
||||
# the `pod 'netdesk_core'` line (and uncomments the platform) rather than
|
||||
# overwriting it.
|
||||
|
||||
platform :ios, '13.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def flutter_root
|
||||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||
end
|
||||
|
||||
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||
return matches[1].strip if matches
|
||||
end
|
||||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||
end
|
||||
|
||||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||
|
||||
flutter_ios_podfile_setup
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
use_modular_headers!
|
||||
|
||||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||
|
||||
# NetDesk Rust core — vendored static xcframework assembled by
|
||||
# packaging/build-ios.sh. Provides VideoToolbox H.264 decode, E2E crypto,
|
||||
# signaling and transport; the Flutter UI binds to it in Phase 3.
|
||||
pod 'netdesk_core', :path => '.'
|
||||
|
||||
target 'RunnerTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_ios_build_settings(target)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,65 @@
|
||||
#
|
||||
# NetDesk by Akronlogic — CocoaPods spec that links the compiled Rust core
|
||||
# (NetDeskCore.xcframework) into the Flutter iOS Runner.
|
||||
#
|
||||
# This file is a TEMPLATE kept under version control in packaging/ios/.
|
||||
# packaging/build-ios.sh copies it to ui/ios/netdesk_core.podspec next to the
|
||||
# freshly-assembled NetDeskCore.xcframework, then adds
|
||||
# pod 'netdesk_core', :path => '.'
|
||||
# to ui/ios/Podfile so `flutter build ipa` links it automatically.
|
||||
#
|
||||
# The xcframework is a *static* library (libnetdesk_core.a, one slice per
|
||||
# platform: ios-arm64 device + ios-arm64-simulator). CocoaPods auto-selects the
|
||||
# right slice per build, so there are no hardcoded slice paths here.
|
||||
#
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'netdesk_core'
|
||||
s.version = '0.1.0'
|
||||
s.summary = 'NetDesk native Rust core (static) for iOS — Akronlogic.'
|
||||
s.description = <<-DESC
|
||||
The compiled NetDesk engine: signaling, end-to-end crypto, the media
|
||||
transport and VideoToolbox H.264 hardware decode (core/src/decode_vt.rs).
|
||||
Vendored as a static xcframework; the Flutter UI binds to it over dart:ffi /
|
||||
flutter_rust_bridge in Phase 3.
|
||||
DESC
|
||||
s.homepage = 'https://akronlogic.com'
|
||||
s.license = { :type => 'Proprietary', :text => 'Copyright Akronlogic. All rights reserved.' }
|
||||
s.author = { 'Akronlogic' => 'https://akronlogic.com' }
|
||||
s.platform = :ios, '13.0'
|
||||
s.source = { :path => '.' }
|
||||
|
||||
# The Rust core, assembled by packaging/build-ios.sh (xcodebuild -create-xcframework).
|
||||
s.vendored_frameworks = 'NetDeskCore.xcframework'
|
||||
|
||||
# System frameworks the VideoToolbox decode path links with
|
||||
# #[link(kind = "framework")] in core/src/decode_vt.rs. A static Rust lib
|
||||
# records these in its metadata but does NOT pull them in at the final app
|
||||
# link, so they must be named here.
|
||||
# CoreMedia — CMVideoFormatDescription / CMSampleBuffer / CMBlockBuffer
|
||||
# VideoToolbox — VTDecompressionSession*
|
||||
# CoreVideo — CVPixelBuffer*
|
||||
# CoreFoundation — CFDictionary/CFString/CFRelease (core-foundation-sys)
|
||||
s.frameworks = 'VideoToolbox', 'CoreMedia', 'CoreVideo', 'CoreFoundation'
|
||||
|
||||
# Vendored static xcframework, no public headers yet (the C ABI header is
|
||||
# generated with cbindgen when Phase 3's core/src/ffi.rs lands — see
|
||||
# docs/build-ios.md). CocoaPods links the archive normally; once the Phase-3
|
||||
# Dart bindings reference the symbols they are retained automatically.
|
||||
#
|
||||
# Before those bindings exist, nothing references the archive, so the linker
|
||||
# may drop it. That is harmless for the build. If you want to *prove* the
|
||||
# archive links end-to-end before Phase 3, force it in by uncommenting:
|
||||
#
|
||||
# s.user_target_xcconfig = {
|
||||
# 'OTHER_LDFLAGS' => '-force_load "${PODS_XCFRAMEWORKS_BUILD_DIR}/netdesk_core/libnetdesk_core.a"'
|
||||
# }
|
||||
#
|
||||
# (Do not combine -force_load with Phase-3 symbol references, or you may hit
|
||||
# duplicate-symbol errors — pick one.)
|
||||
|
||||
s.pod_target_xcconfig = {
|
||||
'DEFINES_MODULE' => 'YES',
|
||||
# Keep the Rust/FFI symbols discoverable for dart:ffi lookups.
|
||||
'STRIP_STYLE' => 'non-global',
|
||||
}
|
||||
end
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,218 @@
|
||||
; ============================================================================
|
||||
; NetDesk by Akronlogic -- Windows installer (NSIS / Modern UI 2)
|
||||
;
|
||||
; Installs NetDesk.exe + the 3 MinGW runtime DLLs into %ProgramFiles%\NetDesk,
|
||||
; creates Start Menu + Desktop shortcuts, registers an uninstaller (Add/Remove
|
||||
; Programs), and offers an OPTIONAL per-user autostart (HKCU Run).
|
||||
;
|
||||
; Build (normally driven by build-installer.ps1):
|
||||
; makensis netdesk.nsi
|
||||
; makensis /DSTAGEDIR=dist\NetDesk-Portable /DOUTFILE=dist\NetDesk-Setup-win64.exe netdesk.nsi
|
||||
; ============================================================================
|
||||
|
||||
Unicode true
|
||||
|
||||
;----------------------------------------------------------------------------
|
||||
; Product metadata
|
||||
;----------------------------------------------------------------------------
|
||||
!define PRODUCT "NetDesk"
|
||||
!define COMPANY "Akronlogic"
|
||||
!define VERSION "0.1.0"
|
||||
!define URL "https://akronlogic.com"
|
||||
!define EXE "NetDesk.exe"
|
||||
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT}"
|
||||
!define AUTORUN_KEY "Software\Microsoft\Windows\CurrentVersion\Run"
|
||||
|
||||
; Staging folder that holds the files to install (produced by build-portable.ps1).
|
||||
; Overridable from the command line with /DSTAGEDIR=...
|
||||
!ifndef STAGEDIR
|
||||
!define STAGEDIR "dist\NetDesk-Portable"
|
||||
!endif
|
||||
!ifndef OUTFILE
|
||||
!define OUTFILE "dist\NetDesk-Setup-win64.exe"
|
||||
!endif
|
||||
|
||||
;----------------------------------------------------------------------------
|
||||
; General
|
||||
;----------------------------------------------------------------------------
|
||||
Name "${PRODUCT} by ${COMPANY}"
|
||||
OutFile "${OUTFILE}"
|
||||
InstallDir "$PROGRAMFILES64\${PRODUCT}"
|
||||
InstallDirRegKey HKLM "Software\${PRODUCT}" "InstallDir"
|
||||
RequestExecutionLevel admin
|
||||
SetCompressor /SOLID lzma
|
||||
BrandingText "${PRODUCT} by ${COMPANY} -- ${URL}"
|
||||
|
||||
; Version info stamped on the setup .exe
|
||||
VIProductVersion "${VERSION}.0"
|
||||
VIAddVersionKey "ProductName" "${PRODUCT}"
|
||||
VIAddVersionKey "CompanyName" "${COMPANY}"
|
||||
VIAddVersionKey "FileDescription" "${PRODUCT} Setup"
|
||||
VIAddVersionKey "FileVersion" "${VERSION}.0"
|
||||
VIAddVersionKey "ProductVersion" "${VERSION}.0"
|
||||
VIAddVersionKey "LegalCopyright" "(c) ${COMPANY}. All rights reserved."
|
||||
|
||||
;----------------------------------------------------------------------------
|
||||
; Modern UI 2
|
||||
;----------------------------------------------------------------------------
|
||||
!include "MUI2.nsh"
|
||||
!include "FileFunc.nsh"
|
||||
!insertmacro GetSize
|
||||
|
||||
!define MUI_ABORTWARNING
|
||||
; Branded NetDesk icon for the setup .exe and the uninstaller (so Windows Search,
|
||||
; Explorer and the installer window all show the green NetDesk mark -- not the
|
||||
; generic NSIS rainbow). Overridable; build-installer.ps1 passes an absolute path
|
||||
; to packaging\netdesk.ico (copied from assets\icon.ico at build time).
|
||||
!ifndef BRANDICON
|
||||
!define BRANDICON "netdesk.ico"
|
||||
!endif
|
||||
!define MUI_ICON "${BRANDICON}"
|
||||
!define MUI_UNICON "${BRANDICON}"
|
||||
|
||||
; Offer to launch NetDesk at the end of setup
|
||||
!define MUI_FINISHPAGE_RUN "$INSTDIR\${EXE}"
|
||||
!define MUI_FINISHPAGE_RUN_TEXT "Launch ${PRODUCT} now"
|
||||
|
||||
; Install pages
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
!insertmacro MUI_PAGE_COMPONENTS
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
|
||||
; Uninstall pages
|
||||
!insertmacro MUI_UNPAGE_CONFIRM
|
||||
!insertmacro MUI_UNPAGE_INSTFILES
|
||||
|
||||
!insertmacro MUI_LANGUAGE "English"
|
||||
|
||||
;----------------------------------------------------------------------------
|
||||
; Clean (re)install: a running NetDesk locks its own files, so close it first.
|
||||
; Reinstalling then overwrites the existing install in place (same directory),
|
||||
; upgrading an older build to this one. Full removal is via the uninstaller.
|
||||
;----------------------------------------------------------------------------
|
||||
Function .onInit
|
||||
ExecWait 'taskkill /F /IM ${EXE} /T'
|
||||
FunctionEnd
|
||||
|
||||
;----------------------------------------------------------------------------
|
||||
; Sections
|
||||
;----------------------------------------------------------------------------
|
||||
Section "!${PRODUCT} (required)" SEC_CORE
|
||||
SectionIn RO
|
||||
SetOverwrite on
|
||||
SetOutPath "$INSTDIR"
|
||||
|
||||
; --- Clean (re)install: stop a running host so its binaries unlock ----------
|
||||
; NetDeskHost.exe is kept alive two ways: a per-user engine/GUI child, AND the
|
||||
; privileged boot-time Scheduled Task "NetDesk" (the host's SERVICE_TASK,
|
||||
; created by `NetDeskHost.exe --install`, running as SYSTEM) whose broker
|
||||
; respawns a session-agent ~every 2s. This installer is elevated
|
||||
; (RequestExecutionLevel admin), so END THE TASK FIRST (stops the respawner),
|
||||
; then force-kill every NetDeskHost.exe tree and the GUI. Errors are ignored
|
||||
; (nothing running on a fresh install is normal). We only /End the task here
|
||||
; (transient) -- never /Disable -- and /Run it again at the end of this
|
||||
; section, so unattended autostart is preserved.
|
||||
nsExec::Exec 'schtasks /End /TN "NetDesk"'
|
||||
Pop $0
|
||||
nsExec::Exec 'taskkill /F /IM NetDeskHost.exe /T'
|
||||
Pop $0
|
||||
nsExec::Exec 'taskkill /F /IM ${EXE} /T'
|
||||
Pop $0
|
||||
|
||||
; Program + the 3 MinGW runtime DLLs that must sit beside it
|
||||
File "${STAGEDIR}\${EXE}"
|
||||
File "${STAGEDIR}\libstdc++-6.dll"
|
||||
File "${STAGEDIR}\libgcc_s_seh-1.dll"
|
||||
File "${STAGEDIR}\libwinpthread-1.dll"
|
||||
File "${STAGEDIR}\README.txt"
|
||||
; NetDesk is one pure-Rust binary now: the native host engine runs in-process,
|
||||
; so there is no separate NetDeskHost.exe to install.
|
||||
|
||||
; Remember where we installed
|
||||
WriteRegStr HKLM "Software\${PRODUCT}" "InstallDir" "$INSTDIR"
|
||||
WriteRegStr HKLM "Software\${PRODUCT}" "Version" "${VERSION}"
|
||||
|
||||
; Start Menu shortcut
|
||||
CreateDirectory "$SMPROGRAMS\${PRODUCT}"
|
||||
CreateShortCut "$SMPROGRAMS\${PRODUCT}\${PRODUCT}.lnk" "$INSTDIR\${EXE}" "" "$INSTDIR\${EXE}" 0
|
||||
CreateShortCut "$SMPROGRAMS\${PRODUCT}\Uninstall ${PRODUCT}.lnk" "$INSTDIR\uninstall.exe"
|
||||
|
||||
; Uninstaller + Add/Remove Programs registration
|
||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${PRODUCT} by ${COMPANY}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${VERSION}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${COMPANY}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "URLInfoAbout" "${URL}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${EXE}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "InstallLocation" "$INSTDIR"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"'
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S'
|
||||
WriteRegDWORD HKLM "${UNINST_KEY}" "NoModify" 1
|
||||
WriteRegDWORD HKLM "${UNINST_KEY}" "NoRepair" 1
|
||||
|
||||
; Estimated size for Add/Remove Programs
|
||||
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
|
||||
IntFmt $0 "0x%08X" $0
|
||||
WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0"
|
||||
|
||||
; --- Bring the host engine back online ------------------------------------
|
||||
; A fresh host `--install` finishes with `schtasks /Run /TN NetDesk`, so do the
|
||||
; same: relaunch the now-updated boot-time broker. No-op if there is no task
|
||||
; yet (first-time install, before the user enables unattended access); the GUI
|
||||
; itself is relaunched by the Finish page's optional "Launch NetDesk now".
|
||||
nsExec::Exec 'schtasks /Run /TN "NetDesk"'
|
||||
Pop $0
|
||||
SectionEnd
|
||||
|
||||
Section "Desktop shortcut" SEC_DESKTOP
|
||||
CreateShortCut "$DESKTOP\${PRODUCT}.lnk" "$INSTDIR\${EXE}" "" "$INSTDIR\${EXE}" 0
|
||||
SectionEnd
|
||||
|
||||
Section /o "Start ${PRODUCT} when I sign in" SEC_AUTOSTART
|
||||
; Per-user autostart (HKCU). Optional, OFF by default. Starts the native host
|
||||
; engine headlessly on login so the PC is reachable unattended.
|
||||
WriteRegStr HKCU "${AUTORUN_KEY}" "${PRODUCT}" '"$INSTDIR\${EXE}" --host'
|
||||
SectionEnd
|
||||
|
||||
;----------------------------------------------------------------------------
|
||||
; Component descriptions
|
||||
;----------------------------------------------------------------------------
|
||||
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
|
||||
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_CORE} "The ${PRODUCT} application and its required runtime files."
|
||||
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_DESKTOP} "Place a ${PRODUCT} shortcut on your Desktop."
|
||||
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_AUTOSTART} "Launch ${PRODUCT} automatically every time you sign in (current user only)."
|
||||
!insertmacro MUI_FUNCTION_DESCRIPTION_END
|
||||
|
||||
;----------------------------------------------------------------------------
|
||||
; Uninstaller
|
||||
;----------------------------------------------------------------------------
|
||||
Section "Uninstall"
|
||||
; Stop the host completely: end the SYSTEM task (stops the broker's respawner),
|
||||
; kill the engine and the GUI, then REMOVE the autostart task entirely so no
|
||||
; orphaned boot-time task is left pointing at a deleted exe.
|
||||
nsExec::Exec 'schtasks /End /TN "NetDesk"'
|
||||
Pop $0
|
||||
ExecWait 'taskkill /F /IM NetDeskHost.exe /T'
|
||||
ExecWait 'taskkill /F /IM ${EXE} /T'
|
||||
nsExec::Exec 'schtasks /Delete /TN "NetDesk" /F'
|
||||
Pop $0
|
||||
Delete "$INSTDIR\${EXE}"
|
||||
Delete "$INSTDIR\NetDeskHost.exe"
|
||||
Delete "$INSTDIR\libstdc++-6.dll"
|
||||
Delete "$INSTDIR\libgcc_s_seh-1.dll"
|
||||
Delete "$INSTDIR\libwinpthread-1.dll"
|
||||
Delete "$INSTDIR\README.txt"
|
||||
Delete "$INSTDIR\uninstall.exe"
|
||||
RMDir "$INSTDIR"
|
||||
|
||||
Delete "$SMPROGRAMS\${PRODUCT}\${PRODUCT}.lnk"
|
||||
Delete "$SMPROGRAMS\${PRODUCT}\Uninstall ${PRODUCT}.lnk"
|
||||
RMDir "$SMPROGRAMS\${PRODUCT}"
|
||||
Delete "$DESKTOP\${PRODUCT}.lnk"
|
||||
|
||||
DeleteRegValue HKCU "${AUTORUN_KEY}" "${PRODUCT}"
|
||||
DeleteRegKey HKLM "${UNINST_KEY}"
|
||||
DeleteRegKey HKLM "Software\${PRODUCT}"
|
||||
SectionEnd
|
||||
@@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
NetDesk by Akronlogic — WiX v4 MSI for enterprise / unattended deployment.
|
||||
|
||||
Installs the portable payload (NetDesk.exe + the 3 MinGW runtime DLLs) to
|
||||
%ProgramFiles%\NetDesk, then runs `NetDesk.exe --config ...` as a deferred
|
||||
custom action to write config.json non-interactively from the deployment
|
||||
properties, and registers the unattended host task.
|
||||
|
||||
Build (see build-msi.ps1 — needs the WiX toolset, which needs the .NET SDK):
|
||||
dotnet tool install --global wix
|
||||
wix build packaging\netdesk.wxs -d Payload="packaging\dist\NetDesk-Portable" -arch x64 -o packaging\dist\NetDesk.msi
|
||||
|
||||
Deploy (GPO / SCCM / Intune, fully silent):
|
||||
msiexec /i NetDesk.msi /qn SERVER="wss://relay.corp/ws" REGKEY="CORP1" PASSWORD="Sup3rSecret!" HOSTID="123456789"
|
||||
|
||||
NOTE: authored on a box that has only the .NET runtime (no SDK, no WiX), so this
|
||||
source has NOT been compiled here. The unattended `--config` custom action it
|
||||
drives IS proven (viewer config_cli_tests + a live `NetDesk.exe --config` run).
|
||||
Verify this .wxs with `wix build` on a box with the WiX toolset before shipping.
|
||||
-->
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<Package Name="NetDesk"
|
||||
Manufacturer="Akronlogic"
|
||||
Version="1.5.0"
|
||||
UpgradeCode="6f9a1d2e-3b4c-4d5e-8f10-ac11de550001"
|
||||
Scope="perMachine">
|
||||
|
||||
<MajorUpgrade DowngradeErrorMessage="A newer version of NetDesk is already installed." />
|
||||
<MediaTemplate EmbedCab="yes" />
|
||||
|
||||
<!-- Unattended-deployment properties (passed on the msiexec command line).
|
||||
PASSWORD is hidden so it never lands in the verbose MSI log. -->
|
||||
<Property Id="SERVER" Secure="yes" />
|
||||
<Property Id="REGKEY" Secure="yes" />
|
||||
<Property Id="PASSWORD" Secure="yes" Hidden="yes" />
|
||||
<Property Id="HOSTID" Secure="yes" />
|
||||
|
||||
<StandardDirectory Id="ProgramFiles64Folder">
|
||||
<Directory Id="INSTALLFOLDER" Name="NetDesk">
|
||||
<Component Id="cmpNetDeskExe" Bitness="always64">
|
||||
<File Id="NetDeskExe" Source="$(Payload)\NetDesk.exe" KeyPath="yes" />
|
||||
</Component>
|
||||
<Component Id="cmpGcc" Bitness="always64">
|
||||
<File Source="$(Payload)\libgcc_s_seh-1.dll" KeyPath="yes" />
|
||||
</Component>
|
||||
<Component Id="cmpStdcpp" Bitness="always64">
|
||||
<File Source="$(Payload)\libstdc++-6.dll" KeyPath="yes" />
|
||||
</Component>
|
||||
<Component Id="cmpPthread" Bitness="always64">
|
||||
<File Source="$(Payload)\libwinpthread-1.dll" KeyPath="yes" />
|
||||
</Component>
|
||||
<Component Id="cmpReadme" Bitness="always64">
|
||||
<File Source="$(Payload)\README.txt" KeyPath="yes" />
|
||||
</Component>
|
||||
</Directory>
|
||||
</StandardDirectory>
|
||||
|
||||
<Feature Id="Main" Title="NetDesk" Level="1">
|
||||
<ComponentRef Id="cmpNetDeskExe" />
|
||||
<ComponentRef Id="cmpGcc" />
|
||||
<ComponentRef Id="cmpStdcpp" />
|
||||
<ComponentRef Id="cmpPthread" />
|
||||
<ComponentRef Id="cmpReadme" />
|
||||
</Feature>
|
||||
|
||||
<!-- After the files are laid down, write config.json from the deployment
|
||||
properties (only when a PASSWORD was supplied), then register the host
|
||||
task so it runs unattended. Both run the just-installed NetDesk.exe. -->
|
||||
<CustomAction Id="WriteConfig" FileRef="NetDeskExe" Execute="deferred"
|
||||
Impersonate="no" Return="ignore"
|
||||
ExeCommand="--config --server "[SERVER]" --reg-key "[REGKEY]" --password "[PASSWORD]" --id "[HOSTID]"" />
|
||||
<CustomAction Id="InstallTask" FileRef="NetDeskExe" Execute="deferred"
|
||||
Impersonate="no" Return="ignore" ExeCommand="--install-service" />
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="WriteConfig" After="InstallFiles" Condition="NOT Installed AND PASSWORD" />
|
||||
<Custom Action="InstallTask" After="WriteConfig" Condition="NOT Installed" />
|
||||
</InstallExecuteSequence>
|
||||
</Package>
|
||||
</Wix>
|
||||
Reference in new issue
Block a user