#!/bin/sh # mvirt installer — https://cli.mvirt.io # # curl -fsSL https://cli.mvirt.io | sh # # The binaries live in the Forgejo generic package registry on git.malte.io; this script is the only # thing cli.mvirt.io serves for the install path. That split is deliberate: the download origin is the # git host, which is external to the mvirt cluster and therefore still reachable during exactly the # outage where someone wants to install the CLI to go look at the cluster. # # Env knobs: # MVIRT_VERSION version to install (default: latest) # MVIRT_INSTALL install dir (default: $HOME/.local/bin) # MVIRT_PKG_BASE package registry (default: the git.malte.io generic registry) # MVIRT_SKILL install the Claude Code skill (default: 1; set 0 to skip) # MVIRT_SKILL_DIR skill dir (default: $HOME/.claude/skills/mvirt) set -eu PKG_BASE="${MVIRT_PKG_BASE:-https://git.malte.io/api/packages/mvirt/generic/mvirt-cli}" VER="${MVIRT_VERSION:-latest}" DEST="${MVIRT_INSTALL:-$HOME/.local/bin}" err() { echo "mvirt-install: $*" >&2; exit 1; } # ---- detect platform -> Rust target triple ------------------------------- OS="$(uname -s)" ARCH="$(uname -m)" case "$OS" in Linux) case "$ARCH" in x86_64|amd64) TRIPLE=x86_64-unknown-linux-musl ;; aarch64|arm64) TRIPLE=aarch64-unknown-linux-musl ;; *) err "unsupported Linux arch: $ARCH" ;; esac ;; Darwin) case "$ARCH" in x86_64) TRIPLE=x86_64-apple-darwin ;; arm64) TRIPLE=aarch64-apple-darwin ;; *) err "unsupported macOS arch: $ARCH" ;; esac ;; *) err "unsupported OS: $OS (Windows? use: irm https://cli.mvirt.io/install.ps1 | iex)" ;; esac # ---- download + checksum plumbing ---------------------------------------- have() { command -v "$1" >/dev/null 2>&1; } have curl || have wget || err "need curl or wget" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT fetch() { # url dest if have curl; then curl -fsSL "$1" -o "$2" else wget -qO "$2" "$1"; fi } sha256_of() { # file if have sha256sum; then sha256sum "$1" | awk '{print $1}' elif have shasum; then shasum -a 256 "$1" | awk '{print $1}' else err "need sha256sum or shasum to verify"; fi } URL="$PKG_BASE/$VER/mvirt-$TRIPLE" echo "mvirt-install: downloading $TRIPLE ($VER) ..." fetch "$URL" "$TMP/mvirt" || err "download failed: $URL" fetch "$URL.sha256" "$TMP/mvirt.sha256" || err "checksum download failed: $URL.sha256" want="$(awk '{print $1}' "$TMP/mvirt.sha256")" [ -n "$want" ] || err "empty checksum" got="$(sha256_of "$TMP/mvirt")" [ "$want" = "$got" ] || err "checksum mismatch (want $want, got $got)" mkdir -p "$DEST" chmod 0755 "$TMP/mvirt" mv -f "$TMP/mvirt" "$DEST/mvirt" echo "mvirt-install: installed -> $DEST/mvirt" "$DEST/mvirt" --version 2>/dev/null || true # ---- credential helper -------------------------------------------------- # Docker AND Podman exec `docker-credential-mvirt` from PATH for registry auth. It is the SAME # binary — mvirt switches into credential-helper mode when invoked under that name (argv[0]). # Expose it as a sibling symlink so both runtimes find it right after install. ln -sf mvirt "$DEST/docker-credential-mvirt" 2>/dev/null \ && echo "mvirt-install: linked docker-credential-mvirt -> mvirt" \ || cp -f "$DEST/mvirt" "$DEST/docker-credential-mvirt" # fallback if symlinks are unavailable # ---- install the Claude Code skill (best-effort; opt out with MVIRT_SKILL=0) ---- # The skill is embedded in the binary, so it is always version-matched — we just ask # the freshly installed mvirt to write it into ~/.claude/skills/mvirt/SKILL.md. # Only done when Claude Code is actually present (a ~/.claude dir), unless the target # dir is set explicitly (MVIRT_SKILL_DIR = opt-in regardless). if [ "${MVIRT_SKILL:-1}" != "0" ]; then if [ -n "${MVIRT_SKILL_DIR:-}" ]; then "$DEST/mvirt" cli skill --install --dir "$MVIRT_SKILL_DIR" >/dev/null 2>&1 \ && echo "mvirt-install: installed Claude Code skill -> $MVIRT_SKILL_DIR/SKILL.md" || true elif [ -d "$HOME/.claude" ]; then "$DEST/mvirt" cli skill --install >/dev/null 2>&1 \ && echo "mvirt-install: installed Claude Code skill -> $HOME/.claude/skills/mvirt/SKILL.md" || true fi fi # ---- add to PATH (idempotent; picks the right rc for the user's shell) ---- add_to_path() { dir="$1" case ":$PATH:" in *":$dir:"*) return 0 ;; esac # already effective line="export PATH=\"$dir:\$PATH\"" case "$(basename "${SHELL:-sh}")" in zsh) rc="${ZDOTDIR:-$HOME}/.zshrc" ;; bash) [ "$(uname -s)" = Darwin ] && rc="$HOME/.bash_profile" || rc="$HOME/.bashrc" ;; fish) rc="$HOME/.config/fish/config.fish"; line="fish_add_path \"$dir\"" ;; *) rc="$HOME/.profile" ;; esac if [ -f "$rc" ] && grep -qF "$line" "$rc" 2>/dev/null; then return 0 # already added on a previous run fi mkdir -p "$(dirname "$rc")" printf '\n# added by mvirt-install (https://cli.mvirt.io)\n%s\n' "$line" >> "$rc" echo "mvirt-install: added $dir to PATH in $rc" echo "mvirt-install: restart your shell (or: exec \$SHELL) to use 'mvirt'" } add_to_path "$DEST" echo "mvirt-install: done — next: mvirt auth login"