diff --git a/release/system/v1.0/services/install_services.sh b/release/system/v1.0/services/install_services.sh new file mode 100755 index 0000000..dd61d25 --- /dev/null +++ b/release/system/v1.0/services/install_services.sh @@ -0,0 +1,324 @@ +#!/bin/bash +#=========================================================================== +# Momentry Core — Service Dependency Build Script +# Builds all 14 service dependencies from source code. +# +# Usage: bash install_services.sh [--prefix $HOME] [--src-dir ./src] +#=========================================================================== +set -euo pipefail + +PREFIX="${PREFIX:-$HOME}" +SRC_DIR="${SRC_DIR:-$(dirname "$0")/src}" +BUILD_DIR="${BUILD_DIR:-$HOME/service_build}" +LOG_DIR="${LOG_DIR:-$HOME/service_logs}" +NCPU=$(sysctl -n hw.ncpu 2>/dev/null || echo 4) + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' + +mkdir -p "$BUILD_DIR" "$LOG_DIR" + +log() { echo -e "${GREEN}[$(date +%H:%M:%S)]${NC} $*" | tee -a "$LOG_DIR/build.log"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*" | tee -a "$LOG_DIR/build.log"; } +fail() { echo -e "${RED}[FAIL]${NC} $*" | tee -a "$LOG_DIR/build.log"; exit 1; } + +# ---- Helper ---- +check_file() { [ -f "$1" ] || fail "Missing: $1"; } +check_dir() { [ -d "$1" ] || fail "Missing: $1"; } +check_bin() { + if ! "$1" -version >/dev/null 2>&1 && ! "$1" --version >/dev/null 2>&1; then + warn "$1: version check returned non-zero (may still be functional)" + fi +} + +#=========================================================================== +# Phase 0: Pre-flight +#=========================================================================== +log "=== Phase 0: Pre-flight Checks ===" +command -v make >/dev/null || fail "make not found (install Xcode CLI: xcode-select --install)" +command -v git >/dev/null || fail "git not found" +command -v curl >/dev/null || fail "curl not found" +command -v cc >/dev/null || fail "cc not found (install Xcode CLI)" +log "Pre-flight OK (make, git, curl, cc available)" + +#=========================================================================== +# Phase 1: Toolchain +#=========================================================================== +log "" +log "=== Phase 1: Toolchain ===" + +# 1a. cmake +log "[1/3] cmake..." +if [ ! -f "$PREFIX/bin/cmake" ]; then + tar xzf "$SRC_DIR/cmake-4.2.0-macos-universal.tar.gz" -C "$BUILD_DIR/" + CMAKE_DIR=$(ls -d "$BUILD_DIR/cmake-"*"-macos-universal" | head -1) + mkdir -p "$PREFIX/bin" + cp "$CMAKE_DIR/CMake.app/Contents/bin/cmake" "$PREFIX/bin/" + cp -r "$CMAKE_DIR/CMake.app/Contents/share" "$PREFIX/share" 2>/dev/null || true + rm -rf "$CMAKE_DIR" + log " cmake installed to $PREFIX/bin/cmake" +else + log " cmake already installed" +fi +check_bin "$PREFIX/bin/cmake" + +# 1b. pyenv +log "[2/3] pyenv..." +if [ ! -d "$PREFIX/.pyenv" ]; then + cp -r "$SRC_DIR/pyenv" "$PREFIX/.pyenv" + log " pyenv installed to $PREFIX/.pyenv" +else + log " pyenv already installed" +fi +export PYENV_ROOT="$PREFIX/.pyenv" +export PATH="$PYENV_ROOT/bin:$PATH" + +# 1c. Python (via pyenv) +log "[3/3] Python 3.11.15..." +if [ ! -f "$PYENV_ROOT/versions/3.11.15/bin/python3" ]; then + log " Building Python 3.11.15 (this takes 5-10 min)..." + PYTHON_CONFIGURE_OPTS="--enable-shared" \ + "$PYENV_ROOT/bin/pyenv" install 3.11.15 2>&1 | tail -3 + log " Python 3.11.15 built" +else + log " Python 3.11.15 already installed" +fi +export PATH="$PYENV_ROOT/versions/3.11.15/bin:$PATH" +check_bin python3.11 + +#=========================================================================== +# Phase 2: Core Services +#=========================================================================== +log "" +log "=== Phase 2: Core Services ===" + +# Make sure build tools are on PATH +export PATH="$PREFIX/bin:$PATH" + +# 2a. x264 (ffmpeg dependency) +log "[1/5] x264..." +if [ ! -f "$PREFIX/ffmpeg_build/lib/libx264.a" ]; then + cd "$SRC_DIR/x264" + ./configure --prefix="$PREFIX/ffmpeg_build" --enable-static --disable-cli > "$LOG_DIR/x264_configure.log" 2>&1 + make -j$NCPU > "$LOG_DIR/x264_make.log" 2>&1 + make install > "$LOG_DIR/x264_install.log" 2>&1 + log " x264 installed to $PREFIX/ffmpeg_build" +else + log " x264 already installed" +fi + +# 2b. freetype (optional, for drawtext filter — not used by baseline pipeline) +log "[2/5] freetype (optional)..." +if [ ! -f "$PREFIX/ffmpeg_build/lib/libfreetype.a" ]; then + tar xzf "$SRC_DIR/freetype-2.13.3.tar.gz" -C "$BUILD_DIR/" + cd "$BUILD_DIR/freetype-2.13.3" + ./configure --prefix="$PREFIX/ffmpeg_build" --enable-static > "$LOG_DIR/freetype_configure.log" 2>&1 + make -j$NCPU > "$LOG_DIR/freetype_make.log" 2>&1 + make install > "$LOG_DIR/freetype_install.log" 2>&1 + rm -rf "$BUILD_DIR/freetype-2.13.3" + log " freetype installed to $PREFIX/ffmpeg_build" +else + log " freetype already installed" +fi + +# 2c. ffmpeg +log "[3/5] ffmpeg..." +if [ ! -f "$PREFIX/ffmpeg_build/bin/ffmpeg" ]; then + tar xf "$SRC_DIR/ffmpeg-7.1.1.tar.xz" -C "$BUILD_DIR/" + cd "$BUILD_DIR/ffmpeg-7.1.1" + # NOTE: drawtext filter needs BOTH libfreetype AND libharfbuzz. + # Add --enable-libfreetype --enable-filter=drawtext after building harfbuzz. + PKG_CONFIG_PATH="$PREFIX/ffmpeg_build/lib/pkgconfig" \ + ./configure \ + --prefix="$PREFIX/ffmpeg_build" \ + --pkg-config-flags="--static" \ + --extra-cflags="-I$PREFIX/ffmpeg_build/include" \ + --extra-ldflags="-L$PREFIX/ffmpeg_build/lib" \ + --enable-gpl \ + --enable-libx264 \ + --enable-encoder=pcm_s16le \ + --enable-encoder=mjpeg \ + --enable-encoder=rawvideo \ + --enable-encoder=aac \ + --enable-encoder=libx264 \ + --enable-muxer=wav \ + --enable-muxer=mp4 \ + --enable-muxer=image2 \ + --enable-muxer=rawvideo \ + --enable-filter=drawbox \ + --enable-filter=scale \ + --enable-filter=select \ + --enable-filter=fps \ + --enable-filter=crop \ + --enable-filter=pad \ + --enable-filter=setpts \ + --enable-filter=format \ + --enable-demuxer=mov,mp4,m4a,avi,matroska,webm \ + --enable-decoder=h264,hevc,mpeg4,mjpeg,aac,mp3,pcm_s16le \ + --disable-doc > "$LOG_DIR/ffmpeg_configure.log" 2>&1 + make -j$NCPU > "$LOG_DIR/ffmpeg_make.log" 2>&1 + make install > "$LOG_DIR/ffmpeg_install.log" 2>&1 + rm -rf "$BUILD_DIR/ffmpeg-7.1.1" + log " ffmpeg installed to $PREFIX/ffmpeg_build/bin" +else + log " ffmpeg already installed" +fi +check_bin "$PREFIX/ffmpeg_build/bin/ffmpeg" +check_bin "$PREFIX/ffmpeg_build/bin/ffprobe" + +# 2d. Redis +log "[4/5] Redis..." +if [ ! -f "$PREFIX/redis/bin/redis-server" ]; then + tar xzf "$SRC_DIR/redis-7.4.3.tar.gz" -C "$BUILD_DIR/" + cd "$BUILD_DIR/redis-7.4.3" + make -j$NCPU > "$LOG_DIR/redis_make.log" 2>&1 + make PREFIX="$PREFIX/redis" install > "$LOG_DIR/redis_install.log" 2>&1 + rm -rf "$BUILD_DIR/redis-7.4.3" + log " Redis installed to $PREFIX/redis" +else + log " Redis already installed" +fi +check_bin "$PREFIX/redis/bin/redis-server" + +# 2e. PostgreSQL +log "[5/5] PostgreSQL..." +if [ ! -f "$PREFIX/pgsql/18.3/bin/postgres" ]; then + tar xzf "$SRC_DIR/postgresql-18.3.tar.gz" -C "$BUILD_DIR/" + cd "$BUILD_DIR/postgresql-18.3" + ./configure --prefix="$PREFIX/pgsql/18.3" > "$LOG_DIR/pg_configure.log" 2>&1 + make -j$NCPU > "$LOG_DIR/pg_make.log" 2>&1 + make install > "$LOG_DIR/pg_install.log" 2>&1 + rm -rf "$BUILD_DIR/postgresql-18.3" + log " PostgreSQL installed to $PREFIX/pgsql/18.3" +else + log " PostgreSQL already installed" +fi +check_bin "$PREFIX/pgsql/18.3/bin/postgres" + +#=========================================================================== +# Phase 3: llama.cpp +#=========================================================================== +log "" +log "=== Phase 3: llama.cpp ===" +if [ ! -f "$PREFIX/llama/bin/llama-server" ]; then + cd "$SRC_DIR/llama.cpp" + cmake -B build -DCMAKE_INSTALL_PREFIX="$PREFIX/llama" > "$LOG_DIR/llama_cmake.log" 2>&1 + cmake --build build --config Release -j$NCPU > "$LOG_DIR/llama_build.log" 2>&1 + mkdir -p "$PREFIX/llama/bin" "$PREFIX/llama/lib" + cp build/bin/llama-server "$PREFIX/llama/bin/" 2>/dev/null || true + cp build/bin/llama-cli "$PREFIX/llama/bin/" 2>/dev/null || true + log " llama.cpp installed to $PREFIX/llama" +else + log " llama.cpp already installed" +fi +[ -f "$PREFIX/llama/bin/llama-server" ] || fail "llama-server not built" + +#=========================================================================== +# Phase 3b: Monitoring Tools +#=========================================================================== +log "" +log "=== Phase 3b: Monitoring Tools ===" + +# macmon (Rust-based network monitor) +log "[1/2] macmon..." +if [ ! -f "$PREFIX/bin/macmon" ]; then + tar xzf "$SRC_DIR/macmon-0.7.2.tar.gz" -C "$BUILD_DIR/" + cd "$BUILD_DIR/macmon-0.7.2" + cargo build --release > "$LOG_DIR/macmon_build.log" 2>&1 + cp target/release/macmon "$PREFIX/bin/macmon" + log " macmon installed to $PREFIX/bin/macmon" +else + log " macmon already installed" +fi +[ -f "$PREFIX/bin/macmon" ] || fail "macmon not built" + +# mactop (Go-based system monitor) +log "[2/2] mactop..." +if [ ! -f "$PREFIX/bin/mactop" ]; then + tar xzf "$SRC_DIR/mactop-latest.tar.gz" -C "$BUILD_DIR/mactop-latest" 2>/dev/null || true + mkdir -p "$BUILD_DIR/mactop" + # Extract tarball (might create a subdirectory with the hash name) + cd "$BUILD_DIR" + if [ -d "$BUILD_DIR/mactop-main" ]; then + rm -rf "$BUILD_DIR/mactop" + mv "$BUILD_DIR/mactop-main" "$BUILD_DIR/mactop" + fi + cd "$BUILD_DIR/mactop" + go build -o mactop . > "$LOG_DIR/mactop_build.log" 2>&1 + cp mactop "$PREFIX/bin/mactop" + log " mactop installed to $PREFIX/bin/mactop" +else + log " mactop already installed" +fi +[ -f "$PREFIX/bin/mactop" ] || fail "mactop not built" + +#=========================================================================== +# Phase 4: Verification +#=========================================================================== +log "" +log "=== Phase 4: Verification ===" +echo "" + +# Test each binary (inline to avoid bash -e/-u function quirks) +PASS=0; FAIL=0 + +if "$PREFIX/bin/cmake" --version >/dev/null 2>&1; then echo " ✅ cmake: $($PREFIX/bin/cmake --version 2>&1 | head -1)"; PASS=$((PASS+1)); else echo " ❌ cmake"; FAIL=$((FAIL+1)); fi +if "$PYENV_ROOT/versions/3.11.15/bin/python3.11" --version >/dev/null 2>&1; then echo " ✅ python3.11: $($PYENV_ROOT/versions/3.11.15/bin/python3.11 --version 2>&1)"; PASS=$((PASS+1)); else echo " ❌ python3.11"; FAIL=$((FAIL+1)); fi +if "$PREFIX/ffmpeg_build/bin/ffmpeg" -version >/dev/null 2>&1; then echo " ✅ ffmpeg: $($PREFIX/ffmpeg_build/bin/ffmpeg -version 2>&1 | head -1)"; PASS=$((PASS+1)); else echo " ❌ ffmpeg"; FAIL=$((FAIL+1)); fi +if "$PREFIX/ffmpeg_build/bin/ffprobe" -version >/dev/null 2>&1; then echo " ✅ ffprobe: $($PREFIX/ffmpeg_build/bin/ffprobe -version 2>&1 | head -1)"; PASS=$((PASS+1)); else echo " ❌ ffprobe"; FAIL=$((FAIL+1)); fi +if "$PREFIX/redis/bin/redis-server" --version >/dev/null 2>&1; then echo " ✅ redis-server: $($PREFIX/redis/bin/redis-server --version 2>&1 | head -1)"; PASS=$((PASS+1)); else echo " ❌ redis-server"; FAIL=$((FAIL+1)); fi +if "$PREFIX/pgsql/18.3/bin/postgres" --version >/dev/null 2>&1; then echo " ✅ postgres: $($PREFIX/pgsql/18.3/bin/postgres --version 2>&1)"; PASS=$((PASS+1)); else echo " ❌ postgres"; FAIL=$((FAIL+1)); fi +if [ -f "$PREFIX/llama/bin/llama-server" ]; then echo " ✅ llama-server: binary exists"; PASS=$((PASS+1)); else echo " ❌ llama-server"; FAIL=$((FAIL+1)); fi +if [ -f "$PYENV_ROOT/bin/pyenv" ]; then echo " ✅ pyenv: installed"; PASS=$((PASS+1)); else echo " ❌ pyenv"; FAIL=$((FAIL+1)); fi +if "$PREFIX/bin/macmon" --version >/dev/null 2>&1; then echo " ✅ macmon: $($PREFIX/bin/macmon --version 2>&1)"; PASS=$((PASS+1)); else echo " ❌ macmon"; FAIL=$((FAIL+1)); fi +if "$PREFIX/bin/mactop" --version >/dev/null 2>&1; then echo " ✅ mactop: $($PREFIX/bin/mactop --version 2>&1)"; PASS=$((PASS+1)); else echo " ❌ mactop"; FAIL=$((FAIL+1)); fi + +echo "" +log "=== Results: $PASS passed, $FAIL failed ===" +[ "$FAIL" -eq 0 ] || exit 1 + +log "=== Build Complete ===" +log "" +log "Service binaries installed at:" +log " cmake: $PREFIX/bin/cmake" +log " python3.11: $PYENV_ROOT/versions/3.11.15/bin/python3.11" +log " ffmpeg: $PREFIX/ffmpeg_build/bin/ffmpeg" +log " ffprobe: $PREFIX/ffmpeg_build/bin/ffprobe" +log " redis-server: $PREFIX/redis/bin/redis-server" +log " postgres: $PREFIX/pgsql/18.3/bin/postgres" +log " llama-server: $PREFIX/llama/bin/llama-server" +log " pyenv: $PYENV_ROOT/bin/pyenv" +log " macmon: $PREFIX/bin/macmon" +log " mactop: $PREFIX/bin/mactop" +log "" +log "To use these tools, add to PATH:" +log " export PATH=\"$PREFIX/bin:$PREFIX/ffmpeg_build/bin:$PREFIX/redis/bin:$PREFIX/pgsql/18.3/bin:$PREFIX/llama/bin:\$PATH\"" + +# ---- Timing Report ---- +log "" +log "=== Build Timing ===" +python3 -c " +import re +with open('$LOG_DIR/build.log') as f: + lines = f.readlines() + +def ts(s): + h,m,sec = s.split(':') + return int(h)*3600 + int(m)*60 + int(sec) + +events = [] +for line in lines: + m = re.search(r'\[(\d{2}:\d{2}:\d{2})\]\s*(.*)', line) + if not m: continue + t, msg = m.group(1), m.group(2).strip() + events.append((ts(t), msg)) + +if len(events) < 2: + print('Not enough timestamps') + exit() + +total = events[-1][0] - events[0][0] +print(f' Total wall-clock time: {total}s ({total/60:.1f} min)') +print(f' Events logged: {len(events)}') +print(f' First: {events[0][1][:60]}') +print(f' Last: {events[-1][1][:60]}') +" 2>/dev/null || true