feat: add macmon + mactop build steps to install_services.sh

This commit is contained in:
Accusys
2026-05-13 22:40:41 +08:00
parent 1642a4b817
commit 65a1b55215

View File

@@ -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