#!/usr/bin/env bash # # Iapetus installer — https://get.iapetus.dev # # Interactive: # curl -fsSL https://get.iapetus.dev | bash # # Non-interactive — pick one or more components: # curl -fsSL https://get.iapetus.dev | bash -s -- --desktop # curl -fsSL https://get.iapetus.dev | bash -s -- --desktop --mcp --develop # # Re-running the installer updates the selected components in place, so the # same one-liner serves as the update command. # # Options: # --desktop Desktop app (Linux .deb, or notarized .app on macOS) # --web Web API agent (iapetus-web-agent, Linux only) # --mcp MCP server (iapetus-mcp-server; on macOS it ships inside the desktop app) # --develop Develop agent (iapetus-develop-agent, Linux only) # --pipelines Pipelines agent (iapetus-pipelines-agent, Linux only) # --component, -c Same as the flags above by name (repeatable; legacy) # --version, -v Release version, e.g. 0.20.0 (default: latest) # --skip-deps Skip the host dependency pre-flight (desktop only) # # Downloads come from the public releases repo # (github.com/demec/iapetus-releases) — no GitHub account or token needed. # Usage of the app is gated by your Iapetus license, not by the download. set -euo pipefail RELEASES_REPO="demec/iapetus-releases" DOWNLOAD_BASE="https://github.com/${RELEASES_REPO}/releases/download" INSTALL_DIR="/usr/local/bin" COMPONENTS=() VERSION="" SKIP_DEPS=false add_component() { # — dedup, validate case "$1" in desktop|web|mcp|develop|pipelines) ;; *) echo "Unknown component: $1 (expected desktop|web|mcp|develop|pipelines)" >&2; exit 1 ;; esac local c for c in ${COMPONENTS[@]+"${COMPONENTS[@]}"}; do [[ "$c" == "$1" ]] && return 0 done COMPONENTS+=("$1") } while [[ $# -gt 0 ]]; do case "$1" in --desktop|--web|--mcp|--develop|--pipelines) add_component "${1#--}"; shift ;; --component|-c) add_component "$2"; shift 2 ;; --version|-v) VERSION="${2#v}"; shift 2 ;; --skip-deps) SKIP_DEPS=true; shift ;; --help|-h) cat <<'EOF' Iapetus installer — https://get.iapetus.dev Interactive: curl -fsSL https://get.iapetus.dev | bash Non-interactive — pick one or more components: curl -fsSL https://get.iapetus.dev | bash -s -- --desktop --mcp Options: --desktop Desktop app (iapetus, .deb) --web Web API agent (iapetus-web-agent) --mcp MCP server (iapetus-mcp-server) --develop Develop agent (iapetus-develop-agent) --pipelines Pipelines agent (iapetus-pipelines-agent) --component, -c Same as the flags above by name (repeatable; legacy) --version, -v Release version, e.g. 0.20.0 (default: latest) --skip-deps Skip the host dependency pre-flight (desktop only) EOF exit 0 ;; *) echo "Unknown option: $1" >&2; exit 1 ;; esac done # --- TTY for interactive prompts (works when piped from curl) --- if [ -r /dev/tty ]; then TTY=/dev/tty else TTY="" fi # --- OS detection --- # On macOS everything ships inside the desktop app (the MCP server included), # so the desktop is the only installable component. OS="$(uname -s)" if [[ "$OS" == "Darwin" ]]; then if [[ ${#COMPONENTS[@]} -eq 0 ]]; then COMPONENTS=(desktop) echo "macOS detected — installing the desktop app (it includes the MCP server)." else for c in "${COMPONENTS[@]}"; do if [[ "$c" != "desktop" ]]; then echo "Error: '$c' is not a separate install on macOS." >&2 echo "The desktop app includes the MCP server; the other agents are Linux-only today." >&2 exit 1 fi done fi fi # --- Interactive component menu (multi-select) --- if [[ ${#COMPONENTS[@]} -eq 0 ]]; then if [[ -z "$TTY" ]]; then echo "Error: no interactive terminal available." >&2 echo "Pass component flags explicitly for non-interactive installs," >&2 echo "e.g. bash -s -- --desktop --mcp" >&2 exit 1 fi cat <<'EOF' Iapetus installer · https://iapetus.dev 1) Desktop app (iapetus) 2) Web API (iapetus-web-agent) 3) MCP Server (iapetus-mcp-server) 4) Develop Agent (iapetus-develop-agent) 5) Pipelines Agent (iapetus-pipelines-agent) EOF printf "Select one or more [e.g. 1 3 5, or 'a' for all]: " read -r CHOICES < "$TTY" if [[ "$CHOICES" =~ ^[Aa]$ ]]; then CHOICES="1 2 3 4 5" fi for CHOICE in ${CHOICES//,/ }; do case "$CHOICE" in 1) add_component desktop ;; 2) add_component web ;; 3) add_component mcp ;; 4) add_component develop ;; 5) add_component pipelines ;; *) echo "Invalid choice: $CHOICE" >&2; exit 1 ;; esac done if [[ ${#COMPONENTS[@]} -eq 0 ]]; then echo "Nothing selected." >&2 exit 1 fi fi # --- Arch (Linux: amd64; macOS: Apple silicon) --- if [[ "$OS" == "Darwin" ]]; then if [[ "$(uname -m)" != "arm64" ]]; then echo "Error: Iapetus for macOS requires Apple silicon (this Mac is $(uname -m))." >&2 exit 1 fi elif [[ "$(uname -m)" != "x86_64" ]]; then echo "Error: unsupported architecture: $(uname -m) (amd64 builds only for now)." >&2 exit 1 fi TMPDIR="$(mktemp -d)" trap 'rm -rf "$TMPDIR"' EXIT download() { # echo "Downloading $(basename "$2")..." if ! curl -fSL --progress-bar "$1" -o "$2"; then echo "Error: download failed: $1" >&2 echo "If you pinned --version, check it exists at https://github.com/${RELEASES_REPO}/releases" >&2 exit 1 fi } # --- Host dependency pre-flight (desktop) ----------------------------- # # Mirrors the checks the app performs on its System page, so first launch # lands on a machine that can actually run VMs. Detection only + one # offered apt install; deeper host setup (sudoers rules, kvm group # membership) is guided inside the app. preflight_desktop_deps() { command -v apt-get &>/dev/null || { echo "Note: apt-get not found — skipping dependency pre-flight." >&2 return 0 } # binary-to-check -> apt package local checks=( "qemu-system-x86_64:qemu-system-x86" "qemu-img:qemu-utils" "cloud-localds:cloud-image-utils" "genisoimage:genisoimage" "dnsmasq:dnsmasq" "iptables:iptables" "virtiofsd:virtiofsd" ) local missing_pkgs=() echo "" echo "Checking host dependencies..." for entry in "${checks[@]}"; do local bin="${entry%%:*}" pkg="${entry##*:}" # virtiofsd installs outside PATH on Debian/Ubuntu. if [[ "$bin" == "virtiofsd" ]]; then if [[ -x /usr/libexec/virtiofsd || -x /usr/lib/qemu/virtiofsd ]] || command -v virtiofsd &>/dev/null; then printf " ok %s\n" "$bin" else printf " MISSING %s (%s)\n" "$bin" "$pkg" missing_pkgs+=("$pkg") fi elif command -v "$bin" &>/dev/null; then printf " ok %s\n" "$bin" else printf " MISSING %s (%s)\n" "$bin" "$pkg" missing_pkgs+=("$pkg") fi done if ! command -v docker &>/dev/null; then echo " MISSING docker (install Docker Engine: https://docs.docker.com/engine/install/)" echo " Docker is needed on the host for compose image distribution." else echo " ok docker" fi if [[ ${#missing_pkgs[@]} -eq 0 ]]; then echo "All host dependencies present." return 0 fi echo "" echo "Missing packages: ${missing_pkgs[*]}" if [[ -n "$TTY" ]]; then printf "Install them now with apt? [Y/n]: " read -r ANSWER < "$TTY" if [[ -z "$ANSWER" || "$ANSWER" =~ ^[Yy] ]]; then sudo apt-get update sudo apt-get install -y "${missing_pkgs[@]}" else echo "Skipped. Install later with:" echo " sudo apt-get install -y ${missing_pkgs[*]}" fi else echo "Install them with:" echo " sudo apt-get install -y ${missing_pkgs[*]}" fi echo "" echo "Note: KVM access (kvm group membership) and sudo rules are set up" echo "inside the app on first launch — follow the System page there." } # --- Installers -------------------------------------------------------- install_desktop_macos() { if [[ -n "$VERSION" ]]; then URL="${DOWNLOAD_BASE}/macos-v${VERSION}/Iapetus-${VERSION}.dmg" else URL="${DOWNLOAD_BASE}/macos-desktop/Iapetus.dmg" fi download "$URL" "${TMPDIR}/Iapetus.dmg" echo "Installing Iapetus.app to /Applications..." MOUNT="${TMPDIR}/mnt" hdiutil attach -quiet -nobrowse -mountpoint "$MOUNT" "${TMPDIR}/Iapetus.dmg" trap 'hdiutil detach -quiet "$MOUNT" 2>/dev/null || true; rm -rf "$TMPDIR"' EXIT rm -rf /Applications/Iapetus.app 2>/dev/null || sudo rm -rf /Applications/Iapetus.app # ditto preserves the code signature and notarization ticket exactly. if ! ditto "$MOUNT/Iapetus.app" /Applications/Iapetus.app 2>/dev/null; then echo "Writing to /Applications needs administrator rights." sudo ditto "$MOUNT/Iapetus.app" /Applications/Iapetus.app fi hdiutil detach -quiet "$MOUNT" echo "Installed Iapetus${VERSION:+ v$VERSION} to /Applications." echo "Launch it from Applications — the engine installs itself as a Login Item on first run." } install_desktop() { if [[ "$OS" == "Darwin" ]]; then install_desktop_macos return fi if [[ -n "$VERSION" ]]; then URL="${DOWNLOAD_BASE}/v${VERSION}/iapetus_${VERSION}_amd64.deb" NAME="iapetus_${VERSION}_amd64.deb" else URL="${DOWNLOAD_BASE}/desktop/iapetus_amd64.deb" NAME="iapetus_amd64.deb" fi if ! command -v dpkg &>/dev/null; then echo "Error: dpkg not found — Iapetus desktop ships as a .deb (Debian/Ubuntu)." >&2 exit 1 fi $SKIP_DEPS || preflight_desktop_deps download "$URL" "${TMPDIR}/${NAME}" echo "Installing .deb..." sudo dpkg -i "${TMPDIR}/${NAME}" || sudo apt-get install -f -y echo "Installed iapetus${VERSION:+ v$VERSION}." } install_binary_agent() { # local bin_name="$1" channel="$2" tag_prefix="$3" if [[ -n "$VERSION" ]]; then URL="${DOWNLOAD_BASE}/${tag_prefix}v${VERSION}/${bin_name}" else URL="${DOWNLOAD_BASE}/${channel}/${bin_name}" fi download "$URL" "${TMPDIR}/${bin_name}" chmod +x "${TMPDIR}/${bin_name}" sudo mv "${TMPDIR}/${bin_name}" "${INSTALL_DIR}/${bin_name}" echo "Installed ${bin_name}${VERSION:+ v$VERSION} to ${INSTALL_DIR}/${bin_name}." } install_component() { # case "$1" in desktop) install_desktop ;; web) install_binary_agent "iapetus-web-agent" "web-agent" "iapetus-web-agent-" ;; mcp) install_binary_agent "iapetus-mcp-server" "mcp-server" "iapetus-mcp-server-" ;; develop) install_binary_agent "iapetus-develop-agent" "develop-agent" "iapetus-develop-agent-" ;; pipelines) install_binary_agent "iapetus-pipelines-agent" "pipelines-agent" "iapetus-pipelines-agent-" ;; esac } echo "" echo "Installing: ${COMPONENTS[*]}" for COMPONENT in "${COMPONENTS[@]}"; do echo "" echo "── ${COMPONENT} ──" install_component "$COMPONENT" # Anonymous, best-effort completion ping per component so we can tell real # installs from mere script fetches (and count every component of a # multi-component run). No PII is sent; never fatal (short timeout). curl -fsS -m 5 -X POST "https://get.iapetus.dev/installed?component=${COMPONENT}" >/dev/null 2>&1 || true done echo "" echo "Done." echo "" echo "Next steps — Iapetus needs a (free) license to run:" echo " 1. Create your account at https://iapetus.dev/sign-up" echo " Verifying your email gets you a free Pro license for 3 months," echo " delivered by email — no credit card." echo " 2. Launch Iapetus and paste the key: Settings → License." echo " Licenses verify offline; no network needed after activation."