#!/usr/bin/env bash # Pemasang alat video lokal untuk agen AI (Claude Code, Codex, Cursor, Gemini CLI): # Hyperframes (CLI, Chrome untuk render, skill inti), skill Remotion, Node.js, FFmpeg. # Disajikan di https://get.mosio.me (apps/get, lihat README.md di sana). # # curl -fsSL https://get.mosio.me | bash # curl -fsSL https://get.mosio.me | bash -s -- --uninstall # curl -fsSL https://get.mosio.me | bash -s -- --help # # Windows memakai install.ps1 dari alamat yang sama: irm https://get.mosio.me | iex # # Yang dipasang skrip ini sendiri ada di ~/.mosio (MOSIO_HOME): Node.js dan FFmpeg hanya # kalau belum ada di PATH, plus CLI hyperframes. sudo hanya diminta di Linux, sekali, # untuk paket sistem yang belum ada (git, unzip, xz, pustaka Chrome, font). # # Semua isi skrip ada di dalam fungsi dan baru dijalankan di baris terakhir, jadi # unduhan yang terpotong tidak menjalankan setengah skrip. set -euo pipefail # FFmpeg: build statis Martin Riedl (ffmpeg.martin-riedl.de), dipin beserta checksum. # Seri 8.x, sama dengan image render Hyperframes. Node.js: rilis terbaru jalur 24 LTS, # dicek dengan SHASUMS256.txt dari nodejs.org. Hyperframes dan skill: versi terbaru. FFMPEG_VERSION=8.1.2 NODE_MAJOR=24 NODE_MIN=22 SITE=https://get.mosio.me ffmpeg_build() { case $1 in linux/amd64) echo 1783011670 ;; linux/arm64) echo 1783010599 ;; macos/amd64) echo 1783018342 ;; macos/arm64) echo 1783011502 ;; esac } ffmpeg_sha256() { case "$1 $2" in "linux/amd64 ffmpeg") echo 56452c0bfc4ee0325cd615d62f46ba8264f62eed34f727c2224c6c84fa7b8719 ;; "linux/amd64 ffprobe") echo c6f2d36e98f9a4445fad0b0be539f4c4faf13fd502116bf131becd53f56cd390 ;; "linux/arm64 ffmpeg") echo ab9e16864b6bf4ae7e13bbdbdc29621be11a5c547c57af8d4250e9fa2f5e6461 ;; "linux/arm64 ffprobe") echo fb78317b81cdeb614533be59e489019b754afd199670666af28f0e9574be395b ;; "macos/amd64 ffmpeg") echo a52ef43883f44c219766d4b3bdde4e635b35465d0b704c01c3a0566b59775df9 ;; "macos/amd64 ffprobe") echo 5408ca588c8c72b0dde3afe676d0a7acf25ef97e55ae6eba5c7bede1cda42695 ;; "macos/arm64 ffmpeg") echo ef1aa60006c7b77ce170c1608c08d8e4ba1c30c5746f2ac986ded932d0ac2c3c ;; "macos/arm64 ffprobe") echo c39787f4af7a3932502d2d48db6f6feaaa836b48a73ef78c32cc3285df61dfaf ;; esac } # Pustaka yang dimuat chrome-headless-shell, dicek lewat ldconfig sebelum Chrome diunduh # (supaya sudo cukup diminta sekali) dan lewat ldd sesudahnya. CHROME_LIBS="libnss3.so libnspr4.so libatk-1.0.so.0 libatk-bridge-2.0.so.0 libcups.so.2 libdrm.so.2 libxkbcommon.so.0 libXcomposite.so.1 libXdamage.so.1 libXfixes.so.3 libXrandr.so.2 libgbm.so.1 libpango-1.0.so.0 libcairo.so.2 libasound.so.2 libdbus-1.so.3" # Nama paket per package manager. Paket apt yang di Ubuntu 24.04+ berganti nama jadi # ...t64 dipilih otomatis (apt_names). packages() { case "$1:$2" in apt:git | dnf:git | pacman:git) echo git ;; apt:unzip | dnf:unzip | pacman:unzip) echo unzip ;; apt:xz) echo xz-utils ;; dnf:xz | pacman:xz) echo xz ;; apt:chrome) echo libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 \ libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2 libdbus-1-3 ;; dnf:chrome) echo nss nspr atk at-spi2-atk cups-libs libdrm libxkbcommon libXcomposite libXdamage \ libXfixes libXrandr mesa-libgbm pango cairo alsa-lib dbus-libs ;; pacman:chrome) echo nss nspr atk at-spi2-core libcups libdrm libxkbcommon libxcomposite libxdamage \ libxfixes libxrandr mesa pango cairo alsa-lib dbus ;; apt:fonts) echo fontconfig fonts-liberation fonts-noto-color-emoji ;; dnf:fonts) echo fontconfig liberation-fonts ;; pacman:fonts) echo fontconfig ttf-liberation noto-fonts-emoji ;; *:ffmpeg) echo ffmpeg ;; esac } if [ -t 2 ]; then BOLD=$'\033[1m' DIM=$'\033[2m' RED=$'\033[31m' GREEN=$'\033[32m' YELLOW=$'\033[33m' RESET=$'\033[0m' else BOLD='' DIM='' RED='' GREEN='' YELLOW='' RESET='' fi step() { printf '\n%s==>%s %s%s%s\n' "$GREEN" "$RESET" "$BOLD" "$*" "$RESET" >&2; } info() { printf ' %s\n' "$*" >&2; } ok() { printf ' %s✓%s %s\n' "$GREEN" "$RESET" "$*" >&2; } warn() { printf ' %s!%s %s\n' "$YELLOW" "$RESET" "$*" >&2; } die() { printf '\n%sGagal:%s %s\n' "$RED" "$RESET" "$*" >&2 DIED=1 exit 1 } have() { command -v "$1" >/dev/null 2>&1; } # Keluaran perintah panjang masuk $LOG; kalau gagal, ekornya ditampilkan. quiet() { printf '\n[%s] $ %s\n' "$(date +%H:%M:%S)" "$*" >>"$LOG" if "$@" >"$LOG" 2>&1; then return 0; fi tail -n 15 "$LOG" | sed 's/^/ /' >&2 info "Log lengkap: $LOG" return 1 } usage() { cat <&2 usage >&2 exit 2 ;; esac shift done [ "${MOSIO_NO_MODIFY_PATH:-0}" = 1 ] && MODIFY_PATH=0 return 0 } detect_platform() { case $(uname -s) in Linux) OS=linux ;; Darwin) OS=macos ;; MINGW* | MSYS* | CYGWIN*) die "Di Windows pakai PowerShell: irm $SITE | iex" ;; *) die "Sistem operasi $(uname -s) belum didukung." ;; esac case $(uname -m) in x86_64 | amd64) ARCH=amd64 ;; arm64 | aarch64) ARCH=arm64 ;; *) die "Arsitektur $(uname -m) belum didukung (hanya x86_64 dan arm64)." ;; esac # Terminal Rosetta di Mac Apple Silicon melapor x86_64; biner arm64 lebih cepat. if [ "$OS" = macos ] && [ "$ARCH" = amd64 ] && [ "$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)" = 1 ]; then ARCH=arm64 fi if [ "$OS" = linux ] && [ -f /etc/alpine-release ]; then die "Alpine (musl) belum didukung: Chrome untuk render butuh glibc. Pakai Ubuntu, Debian, atau Fedora." fi return 0 } # Unduhan hanya lewat HTTPS. fetch() { if have curl; then local progress=-sS [ -t 2 ] && progress=--progress-bar curl -fL --proto '=https' --tlsv1.2 --retry 3 --retry-delay 2 --connect-timeout 20 "$progress" -o "$2" "$1" elif have wget; then wget --https-only -q -O "$2" "$1" else die "Butuh curl atau wget." fi } fetch_text() { if have curl; then curl -fsSL --proto '=https' --tlsv1.2 --retry 3 --connect-timeout 20 "$1" else wget --https-only -qO- "$1" fi } sha256_of() { if have sha256sum; then sha256sum "$1" | cut -d' ' -f1 else shasum -a 256 "$1" | cut -d' ' -f1 fi } verify() { local got got=$(sha256_of "$1") [ "$got" = "$2" ] || die "Checksum $(basename "$1") tidak cocok (dapat $got, harusnya $2). Unduhan rusak; coba lagi." } unzip_to() { mkdir -p "$2" if have unzip; then unzip -q -o "$1" -d "$2" elif have bsdtar; then bsdtar -xf "$1" -C "$2" elif have python3; then python3 -I -m zipfile -e "$1" "$2" else die "Butuh unzip untuk membuka $(basename "$1")." fi } # sudo hanya bisa meminta kata sandi kalau ada terminal; tanpa terminal (CI) pakai sudo -n. as_root() { if [ "$(id -u)" = 0 ]; then "$@" elif [ "$NO_SUDO" = 1 ] || ! have sudo; then return 1 elif { : /dev/null; then sudo "$@" else sudo -n "$@" fi } pkg_manager() { if have apt-get; then echo apt elif have dnf; then echo dnf elif have pacman; then echo pacman fi } apt_names() { local p out="" for p in "$@"; do if apt-cache show "${p}t64" >/dev/null 2>&1; then out="$out ${p}t64"; else out="$out $p"; fi done echo "$out" } APT_UPDATED=0 install_pkgs() { local mgr=$1 shift # Kata sandi sudo diminta di sini, di luar quiet, supaya promptnya terlihat. as_root true || return 1 case $mgr in apt) if [ "$APT_UPDATED" = 0 ]; then # Repo pihak ketiga yang rusak membuat update gagal; paket resmi tetap bisa dipasang. as_root env DEBIAN_FRONTEND=noninteractive apt-get update -qq >>"$LOG" 2>&1 || true APT_UPDATED=1 fi # shellcheck disable=SC2046 # daftar paket sengaja dipecah per kata quiet as_root env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends $(apt_names "$@") ;; dnf) quiet as_root dnf install -y "$@" ;; pacman) quiet as_root pacman -S --needed --noconfirm "$@" ;; *) return 1 ;; esac } ldconfig_list() { ldconfig -p 2>/dev/null || /sbin/ldconfig -p 2>/dev/null || /usr/sbin/ldconfig -p 2>/dev/null || true } chrome_libs_missing() { local libs so missing="" libs=$(ldconfig_list) [ -n "$libs" ] || return 1 for so in $CHROME_LIBS; do grep -q "^[[:space:]]*$so " <<<"$libs" || missing="$missing $so" done [ -n "$missing" ] } fonts_missing() { ! have fc-list || [ -z "$(fc-list 2>/dev/null | head -1)" ] } git_works() { if [ "$OS" = macos ]; then # /usr/bin/git di macOS hanya pembuka installer kalau Command Line Tools belum ada. xcode-select -p >/dev/null 2>&1 && git --version >/dev/null 2>&1 else have git fi } system_packages() { step "Paket sistem" if [ "$OS" = macos ]; then macos_tools return fi local need="" group mgr list="" git_works || need="$need git" have unzip || need="$need unzip" have xz || need="$need xz" if [ "$WITH_HYPERFRAMES" = 1 ]; then chrome_libs_missing && need="$need chrome" fonts_missing && need="$need fonts" fi if [ -z "$need" ]; then ok "Lengkap" return fi mgr=$(pkg_manager) for group in $need; do list="$list $(packages "$mgr" "$group")"; done # shellcheck disable=SC2086 set -- $list if [ -z "$mgr" ] || [ $# -eq 0 ]; then warn "Belum ada:$need. Pasang lewat package manager sistem ini, lalu jalankan skrip ini lagi." return fi info "Perlu dipasang ($mgr):$list" [ "$(id -u)" = 0 ] || [ "$NO_SUDO" = 1 ] || info "sudo mungkin meminta kata sandi akun ini." if install_pkgs "$mgr" "$@"; then ok "Terpasang" else warn "Gagal memasang paket sistem. Pasang sendiri:$list" fi } macos_tools() { if git_works; then ok "Command Line Tools Xcode sudah ada" return fi info "git belum ada: jendela pemasangan Command Line Tools Xcode dibuka. Klik Install." xcode-select --install >/dev/null 2>&1 || true if ! [ -t 2 ]; then die "Pasang Command Line Tools Xcode (xcode-select --install), lalu jalankan perintah ini lagi." fi local waited=0 until git_works; do [ $waited -ge 1800 ] && die "Command Line Tools belum terpasang setelah 30 menit. Jalankan perintah ini lagi setelah selesai." sleep 10 waited=$((waited + 10)) done ok "Command Line Tools Xcode terpasang" } check_disk() { local free free=$(df -Pk "$HOME" 2>/dev/null | awk 'NR == 2 { print $4 }') if [ -n "$free" ] && [ "$free" -lt 2097152 ]; then warn "Sisa disk di $HOME sekitar $((free / 1024)) MB; pemasangan butuh sekitar 1,5 GB." fi return 0 } node_major() { node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0 } ensure_node() { step "Node.js" if have node && have npm && [ "$(node_major)" -ge "$NODE_MIN" ]; then ok "Node.js $(node -v) sudah ada ($(command -v node))" return fi local old="" plat ext arch base sums file sha have node && old=$(node -v) case $OS in linux) plat=linux ext=tar.xz ;; macos) plat=darwin ext=tar.gz ;; esac arch=$ARCH [ "$arch" = amd64 ] && arch=x64 base="https://nodejs.org/dist/latest-v$NODE_MAJOR.x" sums=$(fetch_text "$base/SHASUMS256.txt") || die "Tidak bisa mengambil daftar rilis Node.js dari nodejs.org." file=$(awk -v s="-$plat-$arch.$ext" '{ n = length($2) - length(s) } index($2, "node-v") == 1 && n > 0 && substr($2, n + 1) == s { print $2; exit }' <<<"$sums") sha=$(awk -v f="$file" '$2 == f { print $1; exit }' <<<"$sums") [ -n "$file" ] && [ -n "$sha" ] || die "Tidak menemukan Node.js $NODE_MAJOR untuk $plat-$arch." info "Mengunduh $file" fetch "$base/$file" "$TMP/$file" verify "$TMP/$file" "$sha" rm -rf "$MOSIO_HOME/node.new" mkdir -p "$MOSIO_HOME/node.new" tar -xf "$TMP/$file" -C "$MOSIO_HOME/node.new" --strip-components=1 rm -rf "$MOSIO_HOME/node" mv "$MOSIO_HOME/node.new" "$MOSIO_HOME/node" hash -r ok "Node.js $(node -v) di $MOSIO_HOME/node" [ -z "$old" ] || info "Node.js $old yang lama tidak dihapus; terminal baru memakai versi ini lebih dulu." } ensure_ffmpeg() { step "FFmpeg" if have ffmpeg && have ffprobe; then ok "$(ffmpeg -hide_banner -version 2>/dev/null | head -1 | cut -d' ' -f1-3) sudah ada ($(command -v ffmpeg))" return fi local p="$OS/$ARCH" f mkdir -p "$MOSIO_HOME/bin" for f in ffmpeg ffprobe; do info "Mengunduh $f $FFMPEG_VERSION" fetch "https://ffmpeg.martin-riedl.de/download/$p/$(ffmpeg_build "$p")_$FFMPEG_VERSION/$f.zip" "$TMP/$f.zip" verify "$TMP/$f.zip" "$(ffmpeg_sha256 "$p" "$f")" unzip_to "$TMP/$f.zip" "$TMP/$f.d" mv -f "$TMP/$f.d/$f" "$MOSIO_HOME/bin/$f" chmod 755 "$MOSIO_HOME/bin/$f" done hash -r if "$MOSIO_HOME/bin/ffmpeg" -hide_banner -version >/dev/null 2>&1 && "$MOSIO_HOME/bin/ffprobe" -hide_banner -version >/dev/null 2>&1; then ok "FFmpeg $FFMPEG_VERSION di $MOSIO_HOME/bin" return fi rm -f "$MOSIO_HOME/bin/ffmpeg" "$MOSIO_HOME/bin/ffprobe" [ "$OS" = linux ] || die "FFmpeg hasil unduhan tidak bisa dijalankan di Mac ini." # Build statis ini butuh glibc 2.35+ (Ubuntu 22.04, Debian 12, atau lebih baru). warn "Build FFmpeg statis tidak jalan di sistem ini; memakai paket FFmpeg sistem." install_pkgs "$(pkg_manager)" ffmpeg || true hash -r { have ffmpeg && have ffprobe; } || die "Pasang FFmpeg sendiri (misalnya sudo apt-get install ffmpeg), lalu jalankan perintah ini lagi." ok "$(ffmpeg -hide_banner -version 2>/dev/null | head -1 | cut -d' ' -f1-3) dari paket sistem" } rc_files() { local shell_name shell_name=$(basename "${SHELL:-sh}") echo "$HOME/.profile" if [ -f "$HOME/.bashrc" ] || [ "$shell_name" = bash ]; then echo "$HOME/.bashrc"; fi # bash login shell (Terminal macOS) membaca .bash_profile dan melewati .profile. if [ -f "$HOME/.bash_profile" ]; then echo "$HOME/.bash_profile"; fi if [ -f "$HOME/.zshrc" ] || [ "$shell_name" = zsh ]; then echo "${ZDOTDIR:-$HOME}/.zshrc"; fi } write_env() { step "PATH" mkdir -p "$MOSIO_HOME/bin" : >"$MOSIO_HOME/.installed-by-get-mosio" cat >"$MOSIO_HOME/env" <>"$rc" info "Ditambahkan ke $rc" fi done if [ -d "$HOME/.config/fish" ]; then mkdir -p "$HOME/.config/fish/conf.d" cat >"$HOME/.config/fish/conf.d/mosio.fish" </dev/null | tail -1)" info "Mengunduh Chrome untuk render" local chrome="" missing if quiet hyperframes browser ensure; then chrome=$(hyperframes browser path /dev/null | tail -1) elif [ "$OS/$ARCH" = linux/arm64 ]; then warn "Chrome for Testing tidak tersedia untuk Linux arm64. Pasang Chromium dari paket sistem (misalnya sudo apt-get install chromium)." else warn "Chrome untuk render gagal diunduh. Coba lagi nanti: hyperframes browser ensure" fi if [ "$OS" = linux ] && [ -x "$chrome" ] && have ldd; then missing=$(ldd "$chrome" 2>/dev/null | awk '/not found/ { print $1 }' | tr '\n' ' ') if [ -n "$missing" ]; then info "Chrome masih butuh: $missing" # shellcheck disable=SC2046 install_pkgs "$(pkg_manager)" $(packages "$(pkg_manager)" chrome) || true missing=$(ldd "$chrome" 2>/dev/null | awk '/not found/ { print $1 }' | tr '\n' ' ') [ -z "$missing" ] || warn "Pustaka Chrome belum ada: $missing. Render gagal sampai pustaka ini dipasang." fi fi [ -z "$chrome" ] || ok "Chrome di $chrome" if git_works; then info "Memasang skill inti Hyperframes (hyperframes skills update)" if quiet env GIT_CLONE_PROTECTION_ACTIVE=0 GIT_LFS_SKIP_SMUDGE=1 hyperframes skills update; then ok "Skill Hyperframes terpasang (Claude Code: ~/.claude/skills, agen lain: ~/.agents/skills)" else warn "Skill Hyperframes gagal dipasang. Coba lagi nanti: hyperframes skills update" fi else warn "git belum ada: skill Hyperframes dilewati." fi } install_remotion() { step "Skill Remotion" if ! git_works; then warn "git belum ada: skill Remotion dilewati." return fi # Agen yang sama dengan pilihan hyperframes skills: Claude Code dan direktori universal # (~/.agents/skills, dibaca Codex, Cursor, Gemini CLI, OpenCode, dan lainnya). if quiet env GIT_CLONE_PROTECTION_ACTIVE=0 GIT_LFS_SKIP_SMUDGE=1 npx --yes skills add remotion-dev/skills \ --global --agent claude-code universal --skill '*' --copy --yes; then ok "Skill Remotion terpasang" else warn "Skill Remotion gagal dipasang. Coba lagi nanti: npx skills add remotion-dev/skills --global" fi } summary() { cat >&2 <"$rc.mosio-tmp" || true # cat, bukan mv: izin file dan symlink dotfiles tetap. cat "$rc.mosio-tmp" >"$rc" rm -f "$rc.mosio-tmp" info "Baris PATH dihapus dari $rc" fi done rm -f "$HOME/.config/fish/conf.d/mosio.fish" if [ -f "$MOSIO_HOME/.installed-by-get-mosio" ]; then rm -rf "$MOSIO_HOME" ok "$MOSIO_HOME dihapus" elif [ -e "$MOSIO_HOME" ]; then warn "$MOSIO_HOME bukan buatan skrip ini; tidak dihapus." fi cat >&2 <&2 fi } main() { parse_args "$@" MOSIO_HOME=${MOSIO_HOME:-$HOME/.mosio} case $MOSIO_HOME in / | "$HOME" | "$HOME/") die "MOSIO_HOME tidak boleh / atau \$HOME." ;; esac if [ "$UNINSTALL" = 1 ]; then uninstall return fi detect_platform printf '%sAlat video lokal untuk agen AI%s: Hyperframes, skill Remotion, Node.js, FFmpeg.\n' "$BOLD" "$RESET" >&2 printf '%sTujuan: %s. Batalkan dengan Ctrl+C.%s\n' "$DIM" "$MOSIO_HOME" "$RESET" >&2 TMP=$(mktemp -d "${TMPDIR:-/tmp}/mosio.XXXXXX") trap on_exit EXIT mkdir -p "$MOSIO_HOME" LOG="$MOSIO_HOME/install.log" printf '%s %s/%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$OS" "$ARCH" >"$LOG" # Node.js dan FFmpeg dari pemasangan sebelumnya ikut terdeteksi. case ":$PATH:" in *":$MOSIO_HOME/bin:"*) ;; *) export PATH="$MOSIO_HOME/bin:$MOSIO_HOME/node/bin:$PATH" ;; esac check_disk system_packages ensure_node ensure_ffmpeg write_env if [ "$WITH_HYPERFRAMES" = 1 ]; then install_hyperframes; fi if [ "$WITH_REMOTION" = 1 ]; then install_remotion; fi summary } main "$@"