From 6508940d11b78a052d6493c527b3ec2471988abb Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Fri, 7 Nov 2025 11:48:08 -0700 Subject: [PATCH] ci: Created Loki installation scripts --- scripts/install_loki.ps1 | 139 +++++++++++++++++++++++++ scripts/install_loki.sh | 220 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 359 insertions(+) create mode 100644 scripts/install_loki.ps1 create mode 100644 scripts/install_loki.sh diff --git a/scripts/install_loki.ps1 b/scripts/install_loki.ps1 new file mode 100644 index 0000000..431f32b --- /dev/null +++ b/scripts/install_loki.ps1 @@ -0,0 +1,139 @@ +<# +loki installer (Windows/PowerShell 5+ and PowerShell 7) + +Examples: + powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -useb https://raw.githubusercontent.com/Dark-Alex-17/loki/main/scripts/install_loki.ps1 | iex" + pwsh -c "irm https://raw.githubusercontent.com/Dark-Alex-17/loki/main/scripts/install_loki.ps1 | iex -Version vX.Y.Z" + +Parameters: + -Version (default: latest) + -BinDir (default: %LOCALAPPDATA%\loki\bin on Windows; ~/.local/bin on *nix PowerShell) +#> + +[CmdletBinding()] +param( + [string]$Version = $env:LOKI_VERSION, + [string]$BinDir = $env:BIN_DIR +) + +$Repo = 'Dark-Alex-17/loki' + +function Write-Info($msg) { Write-Host "[loki-install] $msg" } +function Fail($msg) { Write-Error $msg; exit 1 } + +Add-Type -AssemblyName System.Runtime +$isWin = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows) +$isMac = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::OSX) +$isLin = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Linux) + +if ($isWin) { $os = 'windows' } +elseif ($isMac) { $os = 'darwin' } +elseif ($isLin) { $os = 'linux' } +else { Fail "Unsupported OS" } + +switch ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) { + 'X64' { $arch = 'x86_64' } + 'Arm64'{ $arch = 'aarch64' } + default { Fail "Unsupported arch: $([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)" } +} + +if (-not $BinDir) { + if ($isWin) { $BinDir = Join-Path $env:LOCALAPPDATA 'loki\bin' } + else { $home = $env:HOME; if (-not $home) { $home = (Get-Item -Path ~).FullName }; $BinDir = Join-Path $home '.local/bin' } +} +New-Item -ItemType Directory -Force -Path $BinDir | Out-Null + +Write-Info "Target: $os-$arch" + +$apiBase = "https://api.github.com/repos/$Repo/releases" +$relUrl = if ($Version) { "$apiBase/tags/$Version" } else { "$apiBase/latest" } +Write-Info "Fetching release: $relUrl" +try { + $release = Invoke-RestMethod -UseBasicParsing -Headers @{ 'User-Agent' = 'loki-installer' } -Uri $relUrl -Method GET +} catch { Fail "Failed to fetch release metadata. $_" } +if (-not $release.assets) { Fail "No assets found in the release." } + +$candidates = @() +if ($os -eq 'windows') { + if ($arch -eq 'x86_64') { $candidates += 'loki-x86_64-pc-windows-msvc.zip' } + else { $candidates += 'loki-aarch64-pc-windows-msvc.zip' } +} elseif ($os -eq 'darwin') { + if ($arch -eq 'x86_64') { $candidates += 'loki-x86_64-apple-darwin.tar.gz' } + else { $candidates += 'loki-aarch64-apple-darwin.tar.gz' } +} elseif ($os -eq 'linux') { + if ($arch -eq 'x86_64') { + $candidates += 'loki-x86_64-unknown-linux-gnu.tar.gz' + $candidates += 'loki-x86_64-unknown-linux-musl.tar.gz' + } else { + $candidates += 'loki-aarch64-unknown-linux-musl.tar.gz' + } +} else { + Fail "Unsupported OS for this installer: $os" +} + +$asset = $null +foreach ($c in $candidates) { + $asset = $release.assets | Where-Object { $_.name -eq $c } | Select-Object -First 1 + if ($asset) { break } +} +if (-not $asset) { + Write-Error "No matching asset found for $os-$arch. Tried:"; $candidates | ForEach-Object { Write-Error " - $_" } + exit 1 +} + +Write-Info "Selected asset: $($asset.name)" +Write-Info "Download URL: $($asset.browser_download_url)" + +$tmp = New-Item -ItemType Directory -Force -Path ([IO.Path]::Combine([IO.Path]::GetTempPath(), "loki-$(Get-Random)")) +$archive = Join-Path $tmp.FullName 'asset' +try { Invoke-WebRequest -UseBasicParsing -Headers @{ 'User-Agent' = 'loki-installer' } -Uri $asset.browser_download_url -OutFile $archive } catch { Fail "Failed to download asset. $_" } + +$extractDir = Join-Path $tmp.FullName 'extract'; New-Item -ItemType Directory -Force -Path $extractDir | Out-Null + +if ($asset.name -match '\.zip$') { + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::ExtractToDirectory($archive, $extractDir) +} elseif ($asset.name -match '\.tar\.gz$' -or $asset.name -match '\.tgz$') { + $tar = Get-Command tar -ErrorAction SilentlyContinue + if ($tar) { & $tar.FullName -xzf $archive -C $extractDir } + else { Fail "Asset is tar archive but 'tar' is not available." } +} else { + try { Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory($archive, $extractDir) } + catch { + $tar = Get-Command tar -ErrorAction SilentlyContinue + if ($tar) { & $tar.FullName -xf $archive -C $extractDir } else { Fail "Unknown archive format; neither zip nor tar workable." } + } +} + +$bin = $null +Get-ChildItem -Recurse -File $extractDir | ForEach-Object { + if ($isWin) { if ($_.Name -ieq 'loki.exe') { $bin = $_.FullName } } + else { if ($_.Name -ieq 'loki') { $bin = $_.FullName } } +} +if (-not $bin) { Fail "Could not find loki binary inside the archive." } + +if (-not $isWin) { try { & chmod +x -- $bin } catch {} } + +$exec = if ($isWin) { 'loki.exe'} else { 'loki' } +$dest = Join-Path $BinDir $exec +Copy-Item -Force $bin $dest +Write-Info "Installed: $dest" + +if ($isWin) { + $pathParts = ($env:Path -split ';') | Where-Object { $_ -ne '' } + if ($pathParts -notcontains $BinDir) { + $userPath = [Environment]::GetEnvironmentVariable('Path', 'User'); if (-not $userPath) { $userPath = '' } + if (-not ($userPath -split ';' | Where-Object { $_ -eq $BinDir })) { + $newUserPath = if ($userPath.Trim().Length -gt 0) { "$userPath;$BinDir" } else { $BinDir } + [Environment]::SetEnvironmentVariable('Path', $newUserPath, 'User') + Write-Info "Added to User PATH: $BinDir (restart shell to take effect)" + } + } +} else { + if (-not ($env:PATH -split ':' | Where-Object { $_ -eq $BinDir })) { + Write-Info "Note: $BinDir is not in PATH. Add it to your shell profile." + } +} + +Write-Info "Done. Try: loki --help" + diff --git a/scripts/install_loki.sh b/scripts/install_loki.sh new file mode 100644 index 0000000..dd903da --- /dev/null +++ b/scripts/install_loki.sh @@ -0,0 +1,220 @@ +#!/usr/bin/env bash +set -euo pipefail + +# loki installer (Linux/macOS) +# +# Usage examples: +# curl -fsSL https://raw.githubusercontent.com/Dark-Alex-17/loki/main/scripts/install_loki.sh | bash +# curl -fsSL https://raw.githubusercontent.com/Dark-Alex-17/loki/main/scripts/install_loki.sh | bash -s -- --version vX.Y.Z +# BIN_DIR="$HOME/.local/bin" bash scripts/install_loki.sh +# +# Flags / Env: +# --version Release tag (default: latest). Or set LOKI_VERSION. +# --bin-dir Install directory (default: /usr/local/bin or ~/.local/bin). Or set BIN_DIR. + +REPO="Dark-Alex-17/loki" +VERSION="${LOKI_VERSION:-}" +BIN_DIR="${BIN_DIR:-}" + +usage() { + echo "loki installer (Linux/macOS)" + echo + echo "Options:" + echo " --version Release tag (default: latest)" + echo " --bin-dir Install directory (default: /usr/local/bin or ~/.local/bin)" + echo " -h, --help Show help" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --version) VERSION="$2"; shift 2;; + --bin-dir) BIN_DIR="$2"; shift 2;; + -h|--help) usage; exit 0;; + *) echo "Unknown argument: $1" >&2; usage; exit 2;; + esac +done + +if [[ -z "${BIN_DIR}" ]]; then + if [[ -w "/usr/local/bin" ]]; then + BIN_DIR="/usr/local/bin" + else + BIN_DIR="${HOME}/.local/bin" + fi +fi +mkdir -p "${BIN_DIR}" + +log() { + echo "[loki-install] $*" +} + +need_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Error: required command '$1' not found" >&2 + exit 1 + fi +} + +need_cmd uname +need_cmd mktemp +need_cmd tar + +if command -v curl >/dev/null 2>&1; then + DL=curl +elif command -v wget >/dev/null 2>&1; then + DL=wget +else + echo "Error: need curl or wget" >&2 + exit 1 +fi + +UNAME_OS=$(uname -s | tr '[:upper:]' '[:lower:]') +case "$UNAME_OS" in + linux) OS=linux ;; + darwin) OS=darwin ;; + *) echo "Error: unsupported OS '$UNAME_OS'" >&2; exit 1;; +esac + +UNAME_ARCH=$(uname -m) +case "$UNAME_ARCH" in + x86_64|amd64) ARCH=x86_64 ;; + aarch64|arm64) ARCH=aarch64 ;; + *) echo "Error: unsupported arch '$UNAME_ARCH'" >&2; exit 1;; +esac + +log "Target: ${OS}-${ARCH}" + +API_BASE="https://api.github.com/repos/${REPO}/releases" +if [[ -z "${VERSION}" ]]; then + RELEASE_URL="${API_BASE}/latest" +else + RELEASE_URL="${API_BASE}/tags/${VERSION}" +fi + +http_get() { + if [[ "$DL" == "curl" ]]; then + curl -fsSL -H 'User-Agent: loki-installer' "$1" + else + wget -qO- --header='User-Agent: loki-installer' "$1" + fi +} + +TMPDIR="$(mktemp -d)" +trap 'rm -rf "$TMPDIR"' EXIT + +log "Fetching release metadata from $RELEASE_URL" +JSON="$TMPDIR/release.json" +if ! http_get "$RELEASE_URL" > "$JSON"; then + echo "Error: failed to fetch release metadata. Check version tag." >&2 + exit 1 +fi + +ASSET_CANDIDATES=() +if [[ "$OS" == "darwin" ]]; then + if [[ "$ARCH" == "x86_64" ]]; then + ASSET_CANDIDATES+=("loki-x86_64-apple-darwin.tar.gz") + else + ASSET_CANDIDATES+=("loki-aarch64-apple-darwin.tar.gz") + fi +elif [[ "$OS" == "linux" ]]; then + if [[ "$ARCH" == "x86_64" ]]; then + LIBC="musl" + if command -v getconf >/dev/null 2>&1 && getconf GNU_LIBC_VERSION >/dev/null 2>&1; then LIBC="gnu"; fi + if ldd --version 2>&1 | grep -qi glibc; then LIBC="gnu"; fi + + if [[ "$LIBC" == "gnu" ]]; then + ASSET_CANDIDATES+=("loki-x86_64-unknown-linux-gnu.tar.gz") + fi + + ASSET_CANDIDATES+=("loki-x86_64-unknown-linux-musl.tar.gz") + else + ASSET_CANDIDATES+=("loki-aarch64-unknown-linux-musl.tar.gz") + fi +else + echo "Error: unsupported OS for this installer: $OS" >&2; exit 1 +fi + +ASSET_NAME=""; ASSET_URL="" +for candidate in "${ASSET_CANDIDATES[@]}"; do + NAME=$(grep -oE '"name":\s*"[^"]+"' "$JSON" | sed 's/"name":\s*"//; s/"$//' | grep -Fx "$candidate" || true) + if [[ -n "$NAME" ]]; then + ASSET_NAME="$NAME" + ASSET_URL=$(awk -v pat="$NAME" ' + BEGIN{ FS=":"; want=0 } + /"name"/ { + line=$0; + gsub(/^\s+|\s+$/,"",line); + gsub(/"name"\s*:\s*"|"/ ,"", line); + want = (line==pat) ? 1 : 0; + next + } + want==1 && /"browser_download_url"/ { + u=$0; + gsub(/^\s+|\s+$/,"",u); + gsub(/.*"browser_download_url"\s*:\s*"|".*/ ,"", u); + print u; + exit + } + ' "$JSON") + if [[ -n "$ASSET_URL" ]]; then break; fi + fi +done + +if [[ -z "$ASSET_URL" ]]; then + echo "Error: no matching asset found for ${OS}-${ARCH}. Tried:" >&2 + for c in "${ASSET_CANDIDATES[@]}"; do echo " - $c" >&2; done + exit 1 +fi + +log "Selected asset: $ASSET_NAME" +log "Download URL: $ASSET_URL" + +ARCHIVE="$TMPDIR/asset" +if [[ "$DL" == "curl" ]]; then + curl -fL -H 'User-Agent: loki-installer' "$ASSET_URL" -o "$ARCHIVE" +else + wget -q --header='User-Agent: loki-installer' "$ASSET_URL" -O "$ARCHIVE" +fi + +WORK="$TMPDIR/work"; mkdir -p "$WORK" +EXTRACTED_DIR="$WORK/extracted"; mkdir -p "$EXTRACTED_DIR" + +if tar -tf "$ARCHIVE" >/dev/null 2>&1; then + tar -xzf "$ARCHIVE" -C "$EXTRACTED_DIR" +else + if command -v unzip >/dev/null 2>&1; then + unzip -q "$ARCHIVE" -d "$EXTRACTED_DIR" + else + echo "Error: unknown archive format; install 'unzip'" >&2 + exit 1 + fi +fi + +BIN_PATH="" +while IFS= read -r -d '' f; do + base=$(basename "$f") + if [[ "$base" == "loki" ]]; then + BIN_PATH="$f" + break + fi +done < <(find "$EXTRACTED_DIR" -type f -print0) + +if [[ -z "$BIN_PATH" ]]; then + echo "Error: could not find 'loki' binary in the archive" >&2 + exit 1 +fi + +chmod +x "$BIN_PATH" +install -m 0755 "$BIN_PATH" "${BIN_DIR}/loki" + +log "Installed: ${BIN_DIR}/loki" + +case ":$PATH:" in + *":${BIN_DIR}:"*) ;; + *) + log "Note: ${BIN_DIR} is not in PATH. Add it, e.g.:" + log " export PATH=\"${BIN_DIR}:\$PATH\"" + ;; +esac + +log "Done. Try: loki --help" +