Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 169 additions & 16 deletions install
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,22 @@ intro "Install"
INSTALL_DIR=$HOME/.nikcli/bin
mkdir -p "$INSTALL_DIR"

# Detect raw OS once and reuse. The derived `binary_name` (with .exe on
# Windows) is assigned only after the platform-detection block below so the
# `--binary` flow stays consistent with `os=windows` for the install path.
raw_os=$(uname -s)
os=$(echo "$raw_os" | tr '[:upper:]' '[:lower:]')
case "$raw_os" in
Darwin*) os="darwin" ;;
Linux*) os="linux" ;;
MINGW*|MSYS*|CYGWIN*) os="windows" ;;
esac

binary_name=$APP
if [ "$os" = "windows" ]; then
binary_name="$APP.exe"
fi

# ────────────────────────────────────────────────────────────────────────────
# Platform detection / version resolution
# ────────────────────────────────────────────────────────────────────────────
Expand All @@ -209,13 +225,6 @@ if [ -n "$binary_path" ]; then
specific_version="local"
step "Using local binary: ${BOLD}${binary_path}${NC}"
else
raw_os=$(uname -s)
os=$(echo "$raw_os" | tr '[:upper:]' '[:lower:]')
case "$raw_os" in
Darwin*) os="darwin" ;;
Linux*) os="linux" ;;
MINGW*|MSYS*|CYGWIN*) os="windows" ;;
esac

arch=$(uname -m)
if [[ "$arch" == "aarch64" ]]; then
Expand Down Expand Up @@ -358,6 +367,130 @@ check_version() {
# Download & install
# ────────────────────────────────────────────────────────────────────────────

install_deferred=false

# Translate a POSIX path (as seen by bash on MSYS/MINGW) to a Windows path
# suitable for native tooling like powershell.exe. Without cygpath, MSYS POSIX
# paths are not understood by powershell.exe and the helper silently fails.
windows_path() {
if command -v cygpath >/dev/null 2>&1; then
cygpath -w "$1"
else
printf '%s' "$1"
fi
}

# Verify PowerShell is available before mutating the user's filesystem. The
# deferred-replace path stages files first and would otherwise set
# install_deferred=true even when the helper cannot actually launch.
require_powershell() {
if ! command -v powershell.exe >/dev/null 2>&1; then
fail "Cannot replace the running ${APP}.exe: powershell.exe is unavailable"
outro "Aborted"
exit 1
fi
}

install_binary() {
local source=$1
local destination="$INSTALL_DIR/$binary_name"

# Non-Windows installs (or first-time Windows installs without a running
# binary) take the direct-replace path. Surface mv/chmod failures so a
# broken staged source doesn't masquerade as a successful install.
if [ "$binary_name" != "$APP.exe" ] || [ ! -f "$destination" ]; then
if ! mv -f "$source" "$destination"; then
fail "Failed to install ${APP} to ${destination}"
outro "Aborted"
exit 1
fi
chmod 755 "$destination"
return
fi

# Windows with a running binary: try a direct replace first (works when
# the file is unlocked or the rename happens to succeed) and bail out of
# the deferred path with a clear error if it fails for non-lock reasons.
if mv -f "$source" "$destination" 2>/dev/null; then
chmod 755 "$destination"
return
fi

# Require PowerShell before staging so we don't leave a stranded
# ${destination}.new.PID file behind on hosts that lack it.
require_powershell

local pending="${destination}.new.$$"
local helper="${destination}.update.$$.ps1"
if ! mv -f "$source" "$pending"; then
fail "Failed to stage pending ${APP} binary at ${pending}"
outro "Aborted"
exit 1
fi

if ! cat > "$helper" <<'POWERSHELL'
param(
[Parameter(Mandatory = $true)][string]$Source,
[Parameter(Mandatory = $true)][string]$Destination,
[Parameter(Mandatory = $true)][string]$Helper
)

$deadline = [DateTime]::UtcNow.AddMinutes(10)
while ([DateTime]::UtcNow -lt $deadline) {
try {
Move-Item -LiteralPath $Source -Destination $Destination -Force -ErrorAction Stop
Remove-Item -LiteralPath $Helper -Force -ErrorAction SilentlyContinue
exit 0
} catch {
Start-Sleep -Milliseconds 200
}
}

# Deadline reached without a successful move. Clean up the helper and the
# pending source so the install dir does not accumulate stale files. Surface
# a non-zero exit so external monitors can detect the failure.
Remove-Item -LiteralPath $Helper -Force -ErrorAction SilentlyContinue
if (Test-Path -LiteralPath $Source) {
Remove-Item -LiteralPath $Source -Force -ErrorAction SilentlyContinue
}
exit 1
POWERSHELL
then
rm -f "$pending"
fail "Failed to write deferred-replace helper at ${helper}"
outro "Aborted"
exit 1
fi

# Guard against a 0-byte helper (e.g. interrupted heredoc) that would
# otherwise succeed silently and never move the binary.
if [ ! -s "$helper" ]; then
rm -f "$pending" "$helper"
fail "Deferred-replace helper ${helper} is empty"
outro "Aborted"
exit 1
fi

local pending_windows
local destination_windows
local helper_windows
pending_windows=$(windows_path "$pending")
destination_windows=$(windows_path "$destination")
helper_windows=$(windows_path "$helper")

# Launch PowerShell detached so installer exit does not abort it. The
# process is intentionally backgrounded; the user's "Update staged;
# restart nikcli to finish" message documents that finalization happens
# asynchronously once nikcli.exe releases its lock.
powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden \
-File "$helper_windows" \
-Source "$pending_windows" \
-Destination "$destination_windows" \
-Helper "$helper_windows" \
>/dev/null 2>&1 &
install_deferred=true
}

download_and_install() {
local tmp_dir="${TMPDIR:-/tmp}/nikcli_install_$$"
mkdir -p "$tmp_dir"
Expand Down Expand Up @@ -394,9 +527,9 @@ download_and_install() {
unzip -q "$tmp_dir/$filename" -d "$tmp_dir"
fi

local extracted_binary="$tmp_dir/bin/$APP"
local extracted_binary="$tmp_dir/bin/$binary_name"
if [ ! -f "$extracted_binary" ]; then
extracted_binary="$tmp_dir/$ASSET_PREFIX-$target/bin/$APP"
extracted_binary="$tmp_dir/$ASSET_PREFIX-$target/bin/$binary_name"
fi
if [ ! -f "$extracted_binary" ]; then
spinner_stop err "Binary not found in archive at expected path"
Expand All @@ -405,17 +538,33 @@ download_and_install() {
exit 1
fi

mv "$extracted_binary" "$INSTALL_DIR/$APP"
chmod 755 "${INSTALL_DIR}/$APP"
install_binary "$extracted_binary"
rm -rf "$tmp_dir"
spinner_stop ok "Installed to ${BOLD}${INSTALL_DIR}/${APP}${NC}"
if [ "$install_deferred" = true ]; then
spinner_stop ok "Update staged; restart nikcli to finish"
else
spinner_stop ok "Installed to ${BOLD}${INSTALL_DIR}/${binary_name}${NC}"
fi
}

install_from_binary() {
spinner_start "Installing from local binary"
cp "$binary_path" "${INSTALL_DIR}/nikcli"
chmod 755 "${INSTALL_DIR}/nikcli"
spinner_stop ok "Installed to ${BOLD}${INSTALL_DIR}/${APP}${NC}"
local staged_binary="${INSTALL_DIR}/${binary_name}.local.$$"
if ! cp "$binary_path" "$staged_binary"; then
spinner_stop err "Failed to stage ${binary_path} to ${staged_binary}"
outro "Aborted"
exit 1
fi
install_binary "$staged_binary"
# install_binary only consumes the staged copy for the non-deferred path;
# the deferred path moves it to ${destination}.new.$$ itself. In all cases
# the staged file should be gone — guard against accidental leftovers.
rm -f "$staged_binary"
if [ "$install_deferred" = true ]; then
spinner_stop ok "Update staged; restart nikcli to finish"
else
spinner_stop ok "Installed to ${BOLD}${INSTALL_DIR}/${binary_name}${NC}"
fi
}

if [ -n "$binary_path" ]; then
Expand Down Expand Up @@ -503,7 +652,11 @@ fi
# Done
# ────────────────────────────────────────────────────────────────────────────

outro "${GREEN}${APP} ${specific_version} installed${NC}"
if [ "$install_deferred" = true ]; then
outro "${GREEN}${APP} ${specific_version} will finish installing after nikcli exits${NC}"
else
outro "${GREEN}${APP} ${specific_version} installed${NC}"
fi
ui ""
ui " ${DIM}Next steps${NC}"
ui " ${BOLD}cd${NC} <project> ${DIM}# open your project${NC}"
Expand Down
2 changes: 1 addition & 1 deletion packages/inference-dashboard/.astro/content-assets.mjs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export default new Map()
export default new Map();
2 changes: 1 addition & 1 deletion packages/inference-dashboard/.astro/content-modules.mjs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export default new Map()
export default new Map();
Loading
Loading