#!/bin/sh # Cialai installer for Linux and macOS. # # curl -fsSL https://cialai.com.br/install.sh | bash # # Linux: installs the .deb on Debian and Ubuntu, the .rpm on Fedora, RHEL, CentOS and openSUSE and the AppImage # elsewhere, with a menu entry and icons in the user's XDG folders. # macOS: downloads the .dmg for Apple silicon or Intel and copies Cialai.app to /Applications. # Everything comes from https://cialai.com.br/downloads and is checked against SHA256SUMS.txt from the same mirror. # CIALAI_FORMAT=deb, rpm or appimage forces a Linux format; CIALAI_BASE_URL points to another mirror. # Everything runs inside main, called on the last line, so a download cut in the middle executes nothing. set -eu say() { printf '%s\n' "$*"; } fail() { printf 'Cialai installer: %s\n' "$*" >&2; exit 1; } has() { command -v "$1" >/dev/null 2>&1; } fetch() { if has curl; then curl -fsSL --retry 3 -o "$2" "$1" || fail "could not download $1" elif has wget; then wget -q -O "$2" "$1" || fail "could not download $1" else fail "curl or wget is required" fi } as_root() { if [ "$(id -u)" -eq 0 ]; then "$@" elif has sudo; then sudo "$@" else fail "run as root or install sudo" fi } # sha256sum on Linux, shasum on macOS; both print the hash first. digest() { if has sha256sum; then sha256sum "$1" | awk '{ print $1 }' elif has shasum; then shasum -a 256 "$1" | awk '{ print $1 }' else fail "sha256sum or shasum is required" fi } # The distribution decides, not the tools on PATH: apt, dpkg, dnf and zypper are also packaged by Arch, and a # .deb or .rpm installed there bypasses pacman. ID_LIKE names the parents, so Mint, Pop, Rocky and Leap follow. pick_format() { if [ -n "${CIALAI_FORMAT:-}" ]; then printf '%s' "$CIALAI_FORMAT" return fi ids="" if [ -r /etc/os-release ]; then # shellcheck disable=SC1091 ids="$(. /etc/os-release && printf '%s %s' "${ID:-}" "${ID_LIKE:-}")" || ids="" fi case " $ids " in *" debian "* | *" ubuntu "*) if has apt-get && has dpkg; then printf deb; return; fi ;; *" fedora "* | *" rhel "* | *" centos "* | *" suse "* | *" opensuse "*) if has dnf || has yum || has zypper; then printf rpm; return; fi ;; esac printf appimage } # The AppImage has no menu entry outside itself: the .desktop inside says Exec=cialai-desktop, a binary that only # the .deb and the .rpm put on PATH. The runtime extracts the icons on its own, without FUSE, and the entry points # at the installed AppImage. Nothing here fails the installation: without icons the menu shows a generic one. install_desktop_entry() { data_dir="${XDG_DATA_HOME:-$HOME/.local/share}" apps_dir="$data_dir/applications" icons_dir="$data_dir/icons/hicolor" icons=0 if (cd "$tmp" && "$bin_dir/cialai" --appimage-extract 'usr/share/icons/*' >/dev/null 2>&1); then # Tauri keeps the 256 px image under 256x256@2, a folder the icon spec reserves for 512 px; it goes to 256x256. for size in 32x32 128x128 256x256@2; do icon="$tmp/squashfs-root/usr/share/icons/hicolor/$size/apps/cialai-desktop.png" [ -f "$icon" ] || continue mkdir -p "$icons_dir/${size%@2}/apps" \ && cp "$icon" "$icons_dir/${size%@2}/apps/cialai-desktop.png" \ && icons=$((icons + 1)) done fi [ "$icons" -gt 0 ] || say "Could not extract the icons from the AppImage; the menu entry gets a generic icon" mkdir -p "$apps_dir" cat >"$apps_dir/cialai.desktop" </dev/null 2>&1 || true; fi if has gtk-update-icon-cache && [ -f "$icons_dir/index.theme" ]; then gtk-update-icon-cache -q -t "$icons_dir" >/dev/null 2>&1 || true fi say "Menu entry written at $apps_dir/cialai.desktop" } # The mascot, one character per pixel: p pink head, m magenta eyes, u plum antennae and chin, l light face, # s mid pink, and a dot for transparent. banner draws two pixel rows per terminal row with half blocks. mascot() { cat <<'PIXELS' ...........pp........... uu.......pppppp.......uu uu......pppppppp......uu .uu....pppppppppp....uu. ..u...pppppppppppp...u.. ..uu.pppppppppppppp.uu.. ...uppppppppppppppppu... ...uuppppppppppppppuu... ..ppuuppppppppppppuupp.. ..ppuuppppppppppppuupp.. .ppppuuppppppppppuupppp. .ppppuuppppppppppuupppp. ..ppppuppppppppppupppp.. ...pppuuppppppppuuppp... ....pppuppppppppuppp.... .mm.pppuupp..ppuuppp.mm. .mm..ppuup....puupp..mm. .mmm..ppuu.ss.uupp..mmm. .mmmm...uussssuu...mmmm. .msmmm.lsussssusl.mmmsm. .msmmm.lsuussuusl.mmmsm. .mmmmm.lluulluull.mmmmm. .mmmmm.lllullulll.mmmmm. ..mmmm.llllllllll.mmmm.. ..mmmm.llllllllll.mmmm.. ...mmm.llllllllll.mmm... ....mm.llllllllll.mm.... ......llllllllllll...... ......llllllllllll...... .......llllllllll....... ........llllllll........ ........llllllll........ .........llllll......... .........uulluu......... ..........uuuu.......... ...........uu........... PIXELS } letters() { cat <<'LETTERS' ██████╗ ██╗ █████╗ ██╗ █████╗ ██╗ ██╔════╝ ██║ ██╔══██╗ ██║ ██╔══██╗ ██║ ██║ ██║ ███████║ ██║ ███████║ ██║ ██║ ██║ ██╔══██║ ██║ ██╔══██║ ██║ ╚██████╗ ██║ ██║ ██║ ███████╗ ██║ ██║ ██║ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ LETTERS } # Only on a real terminal, so logs and pipes stay plain; NO_COLOR keeps the letters without the mascot. banner() { [ -t 1 ] && [ "${TERM:-dumb}" != dumb ] || return 0 if [ -n "${NO_COLOR:-}" ]; then printf '\n'; letters; printf '\n' return 0 fi cols="$(tput cols 2>/dev/null || printf 80)" { mascot; printf '%s\n' '--'; letters; } | awk -v cols="$cols" -v label="$1" ' BEGIN { c["p"] = 211; c["m"] = 162; c["u"] = 53; c["l"] = 225; c["s"] = 218 split("212 211 205 204 168 162", g, " ") esc = sprintf("%c", 27) } $0 == "--" { part = 2; next } part != 2 { px[++n] = $0; next } { m++; txt[m] = esc "[1;38;5;" g[m] "m" $0 esc "[0m" } END { rows = int((n + 1) / 2) for (r = 1; r <= rows; r++) { top = px[2 * r - 1]; bot = px[2 * r]; line = "" for (i = 1; i <= length(top); i++) { t = substr(top, i, 1); b = substr(bot, i, 1) if (t == "." && (b == "." || b == "")) line = line esc "[0m " else if (t == ".") line = line esc "[0;38;5;" c[b] "m\342\226\204" else if (b == "." || b == "") line = line esc "[0;38;5;" c[t] "m\342\226\200" else line = line esc "[0;38;5;" c[t] ";48;5;" c[b] "m\342\226\200" } out[r] = line esc "[0m" } tag = esc "[38;5;175m" label " installer" esc "[0m " esc "[38;5;244mcialai.com.br" esc "[0m" printf "\n" if (cols >= 72) { for (r = 1; r <= rows; r++) { k = r - 6; side = "" if (k >= 1 && k <= m) side = " " txt[k] if (r == 14) side = " " tag printf " %s%s\n", out[r], side } } else { for (r = 1; r <= rows; r++) printf " %s\n", out[r] printf "\n" for (k = 1; k <= m; k++) printf "%s\n", txt[k] printf "\n%s\n", tag } printf "\n" }' } # Downloads the file, checks it against the mirror checksums and leaves it at $tmp/$file. download_checked() { fetch "$base_url/latest.json" "$tmp/latest.json" version="$(sed -n 's/^ *"version": *"\([^"]*\)".*/\1/p' "$tmp/latest.json" | head -n 1)" say "Downloading Cialai ${version:-latest}, $file" fetch "$base_url/SHA256SUMS.txt" "$tmp/SHA256SUMS.txt" fetch "$base_url/$file" "$tmp/$file" chmod 644 "$tmp/$file" expected="$(awk -v name="$file" '$2 == name { print $1 }' "$tmp/SHA256SUMS.txt")" [ -n "$expected" ] || fail "SHA256SUMS.txt has no entry for $file" actual="$(digest "$tmp/$file")" [ "$expected" = "$actual" ] || fail "checksum mismatch for $file; nothing was installed" say "Checksum verified" } install_linux() { case "$(uname -m)" in x86_64 | amd64) ;; *) fail "Cialai for Linux is published for x86_64 only, and this machine is $(uname -m)" ;; esac format="$(pick_format)" case "$format" in deb) file=Cialai_amd64.deb ;; rpm) file=Cialai_x86_64.rpm ;; appimage) file=Cialai_amd64.AppImage ;; *) fail "unknown CIALAI_FORMAT $format; use deb, rpm or appimage" ;; esac # apt reads local packages as the _apt user, which cannot enter a private temporary folder. chmod 755 "$tmp" download_checked case "$format" in deb) as_root apt-get update -qq as_root env DEBIAN_FRONTEND=noninteractive apt-get install -y "$tmp/$file" say "Cialai ${version} installed. Open it from the applications menu or run cialai-desktop." ;; rpm) if has dnf; then as_root dnf install -y "$tmp/$file" elif has yum; then as_root yum install -y "$tmp/$file" else as_root zypper --non-interactive install --allow-unsigned-rpm "$tmp/$file" fi say "Cialai ${version} installed. Open it from the applications menu or run cialai-desktop." ;; appimage) bin_dir="${XDG_BIN_HOME:-$HOME/.local/bin}" mkdir -p "$bin_dir" # A cp over a running AppImage fails with Text file busy. The rename in the same folder is atomic and the # open Cialai keeps the old file until it closes. cp "$tmp/$file" "$bin_dir/.cialai.new" chmod 755 "$bin_dir/.cialai.new" mv -f "$bin_dir/.cialai.new" "$bin_dir/cialai" say "Cialai ${version} installed at $bin_dir/cialai" install_desktop_entry case ":$PATH:" in *":$bin_dir:"*) say "Run it with: cialai" ;; *) say "Add $bin_dir to PATH or run it with: $bin_dir/cialai" ;; esac # The AppImage runtime is static and mounts through fusermount3 or fusermount; it never loads libfuse.so.2. if ! has fusermount3 && ! has fusermount; then say "If it does not open, install FUSE, the fuse3 or fuse2 package, or run: $bin_dir/cialai --appimage-extract-and-run" fi ;; esac } # The .dmg is mounted read only, Cialai.app is copied over any previous copy in /Applications and the image # is ejected. A running Cialai is closed first, so the copy never replaces files in use. install_macos() { case "$(uname -m)" in arm64) file=Cialai_aarch64.dmg ;; x86_64) file=Cialai_x64.dmg ;; *) fail "Cialai for macOS is published for Apple silicon and Intel, and this machine is $(uname -m)" ;; esac has hdiutil || fail "hdiutil is required" download_checked apps="${CIALAI_APPLICATIONS_DIR:-/Applications}" mount="$tmp/mount" mkdir -p "$mount" hdiutil attach "$tmp/$file" -mountpoint "$mount" -nobrowse -readonly -quiet || fail "could not open $file" app="$(find "$mount" -maxdepth 1 -name '*.app' | head -n 1)" [ -n "$app" ] || { hdiutil detach "$mount" -quiet || true; fail "$file has no application inside"; } name="$(basename "$app")" if pgrep -x "cialai-desktop" >/dev/null 2>&1; then say "Closing the running Cialai" osascript -e 'tell application "Cialai" to quit' >/dev/null 2>&1 || true sleep 2 fi if [ -w "$apps" ]; then rm -rf "${apps:?}/$name" cp -R "$app" "$apps/$name" else as_root rm -rf "$apps/$name" as_root cp -R "$app" "$apps/$name" fi hdiutil detach "$mount" -quiet || true say "Cialai ${version} installed at $apps/$name" say "Open it from Launchpad or run: open -a Cialai" } main() { os="$(uname -s)" case "$os" in Linux) label=Linux ;; Darwin) label=macOS ;; *) fail "this installer is for Linux and macOS; on Windows get the installer at https://cialai.com.br/#baixar" ;; esac banner "$label" base_url="${CIALAI_BASE_URL:-https://cialai.com.br/downloads}" tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT trap 'exit 130' INT TERM case "$os" in Linux) install_linux ;; Darwin) install_macos ;; esac } main "$@"