Skip to content

Instantly share code, notes, and snippets.

@0xle0ne
Created September 5, 2025 16:26
Show Gist options
  • Save 0xle0ne/e36aa07dfe4bc2ac3e3c3c54febb779b to your computer and use it in GitHub Desktop.
Save 0xle0ne/e36aa07dfe4bc2ac3e3c3c54febb779b to your computer and use it in GitHub Desktop.
Nanocl install script
#!/bin/sh
set -eu
OWNER_REPO="next-hat/nanocl"
API_URL="https://api.github.com/repos/$OWNER_REPO/releases/latest"
INSTALL_ROOT="$HOME/.nanocl"
ENV_FILE="$INSTALL_ROOT/env"
WRAPPER_MARK="# Added by Nanocl installer"
say() { printf '%s\n' "$*"; }
err() { printf 'Error: %s\n' "$*" >&2; exit 1; }
have() { command -v "$1" >/dev/null 2>&1; }
# http helpers
http_get() {
if have curl; then
curl -fsSL -H "Accept: application/vnd.github+json" "$1"
elif have wget; then
wget -qO- --header="Accept: application/vnd.github+json" "$1"
else
err "need curl or wget"
fi
}
http_download() {
url="$1"; out="$2"
if have curl; then
curl -fL "$url" -o "$out" >> /dev/null 2>&1
elif have wget; then
wget -qO "$out" "$url" >> /dev/null 2>&1
else
err "need curl or wget"
fi
}
detect_platform() {
OS="$(uname -s 2>/dev/null || echo unknown)"
ARCH="$(uname -m 2>/dev/null || echo unknown)"
case "$OS" in
Linux) os=linux ;;
Darwin) os=mac ;;
*) os=unknown ;;
esac
case "$ARCH" in
x86_64|amd64) arch=amd64 ;;
aarch64|arm64) arch=aarch64 ;;
*) arch=unknown ;;
esac
printf '%s:%s\n' "$os" "$arch"
}
# match asset by pattern inside JSON's browser_download_url values
json_find_asset_url() {
pat="$1"
awk -v p="$pat" '
$0 ~ /"browser_download_url"[[:space:]]*:/ {
if (match($0, /"browser_download_url"[[:space:]]*:[[:space:]]*"([^"]+)"/, u)) {
if (u[1] ~ p) { print u[1]; exit 0 }
}
}
'
}
choose_asset_pattern() {
plat="$1"
case "$plat" in
linux:amd64) echo "nanocl_.*_linux_amd64.tar.gz" ;;
mac:aarch64) echo "nanocl_.*_mac_aarch64.tar.gz" ;;
windows:amd64) echo "nanocl_.*_windows_amd64.tar.gz" ;; # kept for completeness
*) echo "" ;;
esac
}
setup_env_file() {
mkdir -p "$INSTALL_ROOT"
cat > "$ENV_FILE" <<'EOF'
# Nanocl environment setup
export PATH="$HOME/.nanocl/bin:$PATH"
export MANPATH="$HOME/.nanocl/share/man:$MANPATH"
EOF
shell_name="$(basename "${SHELL:-}")"
case "$shell_name" in
bash) rcfile="$HOME/.bashrc" ;;
zsh) rcfile="$HOME/.zshrc" ;;
*) rcfile="$HOME/.profile" ;;
esac
# idempotent append of source line
if [ ! -f "$rcfile" ] || ! grep -q '\. "$HOME/.nanocl/env"' "$rcfile" 2>/dev/null; then
printf '\n%s\n[ -f "$HOME/.nanocl/env" ] && . "$HOME/.nanocl/env"\n' "$WRAPPER_MARK" >> "$rcfile"
say "[*] Added Nanocl env source to $rcfile"
else
say "[*] Nanocl env already configured in $rcfile"
fi
}
fallback_build_from_source() {
say "[*] Falling back to building from source with cargo."
if ! have cargo; then
say "[*] Installing Rust via rustup…"
curl --proto '=https' --tlsv1.2 -fsS https://sh.rustup.rs | sh -s -- -y
[ -f "$HOME/.cargo/env" ] && . "$HOME/.cargo/env"
fi
cargo install nanocl --root "$INSTALL_ROOT"
setup_env_file
say "[✓] Nanocl installed from source. Run: . \"$ENV_FILE\" or open a new shell"
exit 0
}
main() {
say "[*] Installing/updating nanocl in $INSTALL_ROOT"
plat="$(detect_platform)"
pattern="$(choose_asset_pattern "$plat")"
if [ -z "$pattern" ]; then
say "[!] No prebuilt asset pattern for platform: $plat"
fallback_build_from_source
fi
say "[*] Detected platform: $plat"
json="$(http_get "$API_URL")" || err "failed to fetch release metadata"
url="$(printf '%s' "$json" | json_find_asset_url "$pattern")"
if [ -z "${url:-}" ]; then
say "[!] Could not find matching asset in release JSON for pattern: $pattern"
fallback_build_from_source
fi
say "[*] Downloading: $url"
tmpdir="$(mktemp -d)" || err "failed to create tmpdir"
trap 'rm -rf "$tmpdir"' EXIT INT HUP
pkg="$tmpdir/nanocl.tar.gz"
http_download "$url" "$pkg" || err "download failed"
say "[*] Extracting archive to temporary directory..."
# extract into tmpdir
if have tar; then
tar -xzf "$pkg" -C "$tmpdir" || err "failed to extract archive"
else
err "tar required to extract the release"
fi
# find the bin and share directories inside extracted tree
bin_dir="$(find "$tmpdir" -type d -name bin -print -quit || true)"
share_dir="$(find "$tmpdir" -type d -name share -print -quit || true)"
if [ -z "$bin_dir" ] && [ -z "$share_dir" ]; then
err "archive does not contain bin or share directories"
fi
# prepare install dir (do NOT remove whole INSTALL_ROOT; preserve other config)
mkdir -p "$INSTALL_ROOT"
# replace bin
if [ -n "$bin_dir" ]; then
say "[*] Updating $INSTALL_ROOT/bin"
rm -rf "$INSTALL_ROOT/bin"
# move the bin dir into place (rename)
mv "$bin_dir" "$INSTALL_ROOT/bin" || {
# fallback to copy if mv fails
mkdir -p "$INSTALL_ROOT/bin"
cp -r "$bin_dir"/* "$INSTALL_ROOT/bin/" || err "failed to copy bin files"
}
# ensure executables are executable (best-effort)
if [ -f "$INSTALL_ROOT/bin/nanocl" ]; then
chmod +x "$INSTALL_ROOT/bin/nanocl" || true
fi
else
say "[!] No bin directory found in archive; skipping bin update"
fi
# replace share (manpages etc.)
if [ -n "$share_dir" ]; then
say "[*] Updating $INSTALL_ROOT/share"
rm -rf "$INSTALL_ROOT/share"
mv "$share_dir" "$INSTALL_ROOT/share" || {
mkdir -p "$INSTALL_ROOT/share"
cp -r "$share_dir"/* "$INSTALL_ROOT/share/" || err "failed to copy share files"
}
else
say "[*] No share directory found in archive; skipping share update"
fi
# regenerate env and ensure shell rc sources it
setup_env_file
# cleanup happens via trap
say "[✓] Installation/update complete."
say "Run: . \"$ENV_FILE\" (or open a new shell) and try: nanocl --help"
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment