Initial Super N2N desktop application
Build Windows Portable Package / portable (push) Has been cancelled

This commit is contained in:
2026-09-01 13:30:52 +08:00
commit c955401fb4
78 changed files with 8266 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
Super N2N Portable
Run super-n2n.exe to open the desktop control plane.
The directory includes n2n\edge.exe and n2n\supernode.exe. The application starts
them as .\n2n\edge.exe and .\n2n\supernode.exe, so no PATH configuration is required.
Windows WebView2 is required. It is included by default on current Windows 10 and
Windows 11 installations.
The bundled n2n programs are distributed under n2n\LICENSE-n2n.txt.
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
n2n_root="${1:?n2n source path is required}"
make_bin="${2:?MinGW make path is required}"
clean="${3:-0}"
cd "$n2n_root"
if [[ ! -f config.mak || ! -f include/config.h ]]; then
./scripts/hack_fakeautoconf.sh
fi
if [[ "$clean" == "1" ]]; then
"$make_bin" clean
fi
"$make_bin" edge supernode
+150
View File
@@ -0,0 +1,150 @@
[CmdletBinding()]
param(
[switch]$Clean,
[switch]$SkipTests
)
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
$projectRoot = Split-Path -Parent $PSScriptRoot
$n2nRoot = Join-Path $projectRoot 'third_party\n2n'
$cargoManifest = Join-Path $projectRoot 'src-tauri\Cargo.toml'
$packageScript = Join-Path $PSScriptRoot 'package-portable.ps1'
$n2nBuildScript = Join-Path $PSScriptRoot 'build-n2n.sh'
$archivePath = Join-Path $projectRoot 'release\super-n2n-portable.zip'
$checksumPath = $archivePath + '.sha256'
function Get-RequiredCommand {
param(
[string[]]$Names,
[string]$Purpose
)
foreach ($name in $Names) {
$command = Get-Command $name -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1
if ($command) {
return $command.Source
}
}
throw "Missing required tool for $Purpose. Expected one of: $($Names -join ', ')."
}
function Get-GitBash {
param([string]$GitExecutable)
$gitRoot = Split-Path -Parent (Split-Path -Parent $GitExecutable)
$candidatePaths = @(
(Join-Path $gitRoot 'bin\bash.exe')
)
$candidatePaths += Get-Command bash.exe -CommandType Application -All -ErrorAction SilentlyContinue | ForEach-Object Source
foreach ($bashPath in $candidatePaths | Select-Object -Unique) {
if (-not (Test-Path -LiteralPath $bashPath)) {
continue
}
$bashDirectory = Split-Path -Parent $bashPath
$gitUnixBinCandidates = @(
$bashDirectory,
(Join-Path (Split-Path -Parent $bashDirectory) 'usr\bin')
)
foreach ($gitUnixBin in $gitUnixBinCandidates) {
if ((Test-Path -LiteralPath (Join-Path $gitUnixBin 'sh.exe')) -and
(Test-Path -LiteralPath (Join-Path $gitUnixBin 'cygpath.exe'))) {
return [pscustomobject]@{
Bash = $bashPath
UnixBin = $gitUnixBin
}
}
}
}
throw 'Git Bash was not found. Install Git for Windows.'
}
function Invoke-Checked {
param(
[string]$Executable,
[string[]]$Arguments
)
& $Executable @Arguments
if ($LASTEXITCODE -ne 0) {
throw "Command failed with exit code ${LASTEXITCODE}: $Executable $($Arguments -join ' ')"
}
}
if ($env:OS -ne 'Windows_NT') {
throw 'This script builds the Windows portable package and must run on Windows.'
}
$git = Get-RequiredCommand -Names @('git.exe', 'git') -Purpose 'source checkout'
$npm = Get-RequiredCommand -Names @('npm.cmd', 'npm') -Purpose 'frontend dependency installation'
$cargo = Get-RequiredCommand -Names @('cargo.exe', 'cargo') -Purpose 'Tauri compilation'
$gitBash = Get-GitBash -GitExecutable $git
$make = Get-RequiredCommand -Names @('mingw32-make.exe', 'mingw32-make') -Purpose 'n2n compilation'
$null = Get-RequiredCommand -Names @('gcc.exe', 'gcc') -Purpose 'n2n compilation'
$null = Get-RequiredCommand -Names @('windres.exe', 'windres') -Purpose 'n2n Windows resource compilation'
# Make GNU utilities (rm, cp, gzip) available to n2n's Makefile on Windows.
$env:Path = "$($gitBash.UnixBin);$env:Path"
$env:SHELL = Join-Path $gitBash.UnixBin 'sh.exe'
Push-Location $projectRoot
try {
Invoke-Checked $git @('submodule', 'sync')
Invoke-Checked $git @('submodule', 'update', '--init')
if (-not (Test-Path -LiteralPath $n2nRoot)) {
throw "The n2n submodule was not initialized: $n2nRoot"
}
$cygpath = Join-Path $gitBash.UnixBin 'cygpath.exe'
$n2nRootUnix = (& $cygpath -u $n2nRoot).Trim()
$makeUnix = (& $cygpath -u $make).Trim()
$n2nBuildScriptUnix = (& $cygpath -u $n2nBuildScript).Trim()
Invoke-Checked $gitBash.Bash @($n2nBuildScriptUnix, $n2nRootUnix, $makeUnix, $(if ($Clean) { '1' } else { '0' }))
foreach ($binary in @('edge.exe', 'supernode.exe')) {
if (-not (Test-Path -LiteralPath (Join-Path $n2nRoot $binary))) {
throw "n2n build did not produce $binary."
}
}
Invoke-Checked $npm @('ci')
if (-not $SkipTests) {
Invoke-Checked $cargo @('test', '--manifest-path', $cargoManifest)
}
& $packageScript -Clean:$Clean
if ($LASTEXITCODE -ne 0) {
throw "Portable package script failed with exit code $LASTEXITCODE."
}
if (-not (Test-Path -LiteralPath $archivePath)) {
throw "Portable archive was not created: $archivePath"
}
$stream = [System.IO.File]::OpenRead($archivePath)
try {
$sha256 = [System.Security.Cryptography.SHA256]::Create()
try {
$hashBytes = $sha256.ComputeHash($stream)
$hash = -join ($hashBytes | ForEach-Object { $_.ToString('x2') })
}
finally {
$sha256.Dispose()
}
}
finally {
$stream.Dispose()
}
"$hash *$(Split-Path -Leaf $archivePath)" | Set-Content -LiteralPath $checksumPath -Encoding ascii
Write-Output "Build completed: $archivePath"
Write-Output "SHA-256 checksum: $checksumPath"
}
finally {
Pop-Location
}
+102
View File
@@ -0,0 +1,102 @@
param(
[switch]$Clean
)
$ErrorActionPreference = 'Stop'
$projectRoot = Split-Path -Parent $PSScriptRoot
$n2nRoot = Join-Path $projectRoot 'third_party\n2n'
$releaseRoot = Join-Path $projectRoot 'release'
$portableRoot = Join-Path $projectRoot 'release\super-n2n-portable'
$stagingRoot = Join-Path $releaseRoot 'super-n2n-portable-staging'
$archivePath = Join-Path $releaseRoot 'super-n2n-portable.zip'
$releaseRootFull = [System.IO.Path]::GetFullPath($releaseRoot)
$portableRootFull = [System.IO.Path]::GetFullPath($portableRoot)
$stagingRootFull = [System.IO.Path]::GetFullPath($stagingRoot)
$archivePathFull = [System.IO.Path]::GetFullPath($archivePath)
if ((Split-Path -Parent $portableRootFull) -ne $releaseRootFull -or (Split-Path -Leaf $portableRootFull) -ne 'super-n2n-portable') {
throw "Refusing to replace an unexpected portable output directory: $portableRootFull"
}
if ((Split-Path -Parent $stagingRootFull) -ne $releaseRootFull -or (Split-Path -Leaf $stagingRootFull) -ne 'super-n2n-portable-staging') {
throw "Refusing to replace an unexpected staging directory: $stagingRootFull"
}
if ((Split-Path -Parent $archivePathFull) -ne $releaseRootFull -or (Split-Path -Leaf $archivePathFull) -ne 'super-n2n-portable.zip') {
throw "Refusing to replace an unexpected portable archive: $archivePathFull"
}
if ($Clean) {
if (Test-Path -LiteralPath $portableRoot) {
Remove-Item -LiteralPath $portableRoot -Recurse -Force
}
if (Test-Path -LiteralPath $archivePath) {
Remove-Item -LiteralPath $archivePath -Force
}
}
Push-Location $projectRoot
try {
& npm.cmd run tauri:portable:raw
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
$files = @{
'super-n2n.exe' = Join-Path $projectRoot 'src-tauri\target\release\super-n2n.exe'
'n2n\edge.exe' = Join-Path $n2nRoot 'edge.exe'
'n2n\supernode.exe' = Join-Path $n2nRoot 'supernode.exe'
'n2n\LICENSE-n2n.txt' = Join-Path $n2nRoot 'LICENSE'
'README.txt' = Join-Path $projectRoot 'scripts\PORTABLE-README.txt'
}
foreach ($entry in $files.GetEnumerator()) {
if (-not (Test-Path -LiteralPath $entry.Value)) {
throw "Required portable artifact is missing: $($entry.Value)"
}
}
if (Test-Path -LiteralPath $stagingRoot) {
Remove-Item -LiteralPath $stagingRoot -Recurse -Force
}
New-Item -ItemType Directory -Force -Path $stagingRoot | Out-Null
foreach ($entry in $files.GetEnumerator()) {
$destination = Join-Path $stagingRoot $entry.Key
$destinationDirectory = Split-Path -Parent $destination
New-Item -ItemType Directory -Force -Path $destinationDirectory | Out-Null
Copy-Item -LiteralPath $entry.Value -Destination $destination -Force
}
Compress-Archive -Path (Join-Path $stagingRoot '*') -DestinationPath $archivePath -CompressionLevel Optimal -Force
New-Item -ItemType Directory -Force -Path $portableRoot | Out-Null
foreach ($legacyName in @('edge.exe', 'supernode.exe', 'LICENSE-n2n.txt')) {
$legacyPath = Join-Path $portableRoot $legacyName
if (Test-Path -LiteralPath $legacyPath) {
try {
Remove-Item -LiteralPath $legacyPath -Force
}
catch {
Write-Warning "Could not remove legacy artifact $legacyPath because it is in use."
}
}
}
foreach ($entry in $files.GetEnumerator()) {
$destination = Join-Path $portableRoot $entry.Key
$destinationDirectory = Split-Path -Parent $destination
New-Item -ItemType Directory -Force -Path $destinationDirectory | Out-Null
try {
Copy-Item -LiteralPath $entry.Value -Destination $destination -Force
}
catch {
Write-Warning "Could not update $destination because it is in use. The zip archive is current."
}
}
Write-Output "Portable package created: $archivePath"
}
finally {
if (Test-Path -LiteralPath $stagingRoot) {
Remove-Item -LiteralPath $stagingRoot -Recurse -Force
}
Pop-Location
}