messenger/install.sh
2026-03-06 00:15:13 -08:00

364 lines
12 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
set -euo pipefail
# =============================================================================
# Messenger Service - Universal Installer
# =============================================================================
#
# Installs the iMessage sync service on any Mac with three modes:
#
# --mode shared Use life-manager's PostgreSQL (messenger tables in same DB)
# --mode local Local PostgreSQL or SQLite, syncs to life-manager on demand
# --mode standalone Fully standalone, no life-manager connection
#
# Examples:
# ./install.sh --mode shared \
# --db-url postgresql://lilith@plum:25471/life_manager \
# --life-manager-url http://plum:3700
#
# ./install.sh --mode local \
# --life-manager-url http://10.0.0.5:3700
#
# ./install.sh --mode standalone
#
# ./install.sh # Interactive — prompts for everything
# =============================================================================
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
CONFIG_DIR="$HOME/.config/messenger-imessage"
ENV_FILE="$CONFIG_DIR/.env"
print_header() {
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${CYAN} Messenger Service — Universal Installer${NC}"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
}
print_step() { echo -e "${GREEN}${NC} $1"; }
print_info() { echo -e "${BLUE}${NC} $1"; }
print_warn() { echo -e "${YELLOW}${NC} $1"; }
print_error() { echo -e "${RED}${NC} $1"; }
print_ok() { echo -e "${GREEN}${NC} $1"; }
# ─── Parse Arguments ─────────────────────────────────────────────────────────
MODE=""
DB_URL=""
LIFE_MANAGER_URL=""
API_KEY=""
SERVICE_PORT="3100"
while [[ $# -gt 0 ]]; do
case "$1" in
--mode) MODE="$2"; shift 2 ;;
--db-url) DB_URL="$2"; shift 2 ;;
--life-manager-url) LIFE_MANAGER_URL="$2"; shift 2 ;;
--api-key) API_KEY="$2"; shift 2 ;;
--port) SERVICE_PORT="$2"; shift 2 ;;
--help|-h)
echo "Usage: ./install.sh [options]"
echo ""
echo "Options:"
echo " --mode <shared|local|standalone> Database mode"
echo " --db-url <url> PostgreSQL connection URL"
echo " --life-manager-url <url> Life-manager API URL"
echo " --api-key <key> Service API key"
echo " --port <port> Service port (default: 3100)"
echo ""
echo "Without options, runs interactively."
exit 0
;;
*)
print_error "Unknown option: $1"
exit 1
;;
esac
done
# ─── Interactive Mode ────────────────────────────────────────────────────────
interactive_setup() {
echo "Select database mode:"
echo ""
echo " ${CYAN}1) shared${NC} — Use life-manager's PostgreSQL"
echo " (messenger tables alongside life-manager tables)"
echo ""
echo " ${CYAN}2) local${NC} — Local Docker PostgreSQL"
echo " (separate DB, optionally syncs to life-manager)"
echo ""
echo " ${CYAN}3) standalone${NC} — Fully standalone, no life-manager"
echo ""
read -p "Mode [1/2/3]: " mode_choice
case "$mode_choice" in
1|shared) MODE="shared" ;;
2|local) MODE="local" ;;
3|standalone) MODE="standalone" ;;
*)
print_error "Invalid choice"
exit 1
;;
esac
if [[ "$MODE" == "shared" ]]; then
echo ""
read -p "PostgreSQL URL [postgresql://lilith@localhost:25471/life_manager]: " DB_URL
DB_URL="${DB_URL:-postgresql://lilith@localhost:25471/life_manager}"
fi
if [[ "$MODE" != "standalone" ]]; then
echo ""
read -p "Life-manager API URL [http://localhost:3700]: " LIFE_MANAGER_URL
LIFE_MANAGER_URL="${LIFE_MANAGER_URL:-http://localhost:3700}"
fi
echo ""
read -p "Service port [$SERVICE_PORT]: " port_input
SERVICE_PORT="${port_input:-$SERVICE_PORT}"
echo ""
read -p "Service API key (leave blank to generate): " API_KEY
if [[ -z "$API_KEY" ]]; then
API_KEY=$(openssl rand -base64 32)
print_info "Generated API key: $API_KEY"
fi
}
# ─── Generate .env ───────────────────────────────────────────────────────────
generate_env() {
mkdir -p "$CONFIG_DIR"
local db_url="$DB_URL"
# Default DB URL based on mode
if [[ -z "$db_url" ]]; then
case "$MODE" in
shared) db_url="postgresql://lilith@localhost:25471/life_manager" ;;
local) db_url="postgresql://postgres:devpassword@localhost:25433/messenger" ;;
standalone) db_url="postgresql://postgres:devpassword@localhost:25433/messenger" ;;
esac
fi
cat > "$ENV_FILE" <<EOF
# Generated by install.sh — $(date)
# Mode: $MODE
# Database
DB_MODE=$MODE
DATABASE_URL=$db_url
# Service
PORT=$SERVICE_PORT
SERVICE_API_KEY=$API_KEY
NODE_ENV=production
# Life Manager (omit for standalone mode)
LIFE_MANAGER_API_URL=${LIFE_MANAGER_URL:-}
EOF
chmod 600 "$ENV_FILE"
print_ok "Generated $ENV_FILE"
}
# ─── Install Backend ─────────────────────────────────────────────────────────
install_backend() {
print_step "Installing backend dependencies..."
cd "$SCRIPT_DIR/imessage-sync/backend"
if command -v bun &>/dev/null; then
bun install --frozen-lockfile 2>/dev/null || bun install
print_ok "Dependencies installed (bun)"
elif command -v npm &>/dev/null; then
npm ci 2>/dev/null || npm install
print_ok "Dependencies installed (npm)"
else
print_error "Neither bun nor npm found"
exit 1
fi
# Build
print_step "Building backend..."
if command -v bun &>/dev/null; then
bun run build
else
npm run build
fi
print_ok "Backend built"
}
# ─── Local Docker (for local/standalone modes) ──────────────────────────────
setup_local_docker() {
if [[ "$MODE" == "shared" ]]; then
print_info "Shared mode — skipping local Docker setup"
return
fi
print_step "Starting local Docker containers..."
cd "$SCRIPT_DIR/imessage-sync"
docker compose up -d
print_ok "Local PostgreSQL + Redis running"
}
# ─── Create LaunchDaemon / systemd ───────────────────────────────────────────
create_service() {
if [[ "$OSTYPE" == "darwin"* ]]; then
create_launchd_service
elif command -v systemctl &>/dev/null; then
create_systemd_service
else
print_warn "No service manager detected. Start manually:"
print_info " cd $SCRIPT_DIR/imessage-sync/backend && node dist/main.js"
fi
}
create_launchd_service() {
local plist_dir="$HOME/Library/LaunchAgents"
local plist_file="$plist_dir/com.lilith.messenger-imessage.plist"
local backend_dir="$SCRIPT_DIR/imessage-sync/backend"
mkdir -p "$plist_dir"
cat > "$plist_file" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.lilith.messenger-imessage</string>
<key>ProgramArguments</key>
<array>
<string>$(which node)</string>
<string>$backend_dir/dist/main.js</string>
</array>
<key>WorkingDirectory</key>
<string>$backend_dir</string>
<key>EnvironmentVariables</key>
<dict>
<key>NODE_ENV</key>
<string>production</string>
<key>ENV_FILE</key>
<string>$ENV_FILE</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>StandardOutPath</key>
<string>$CONFIG_DIR/stdout.log</string>
<key>StandardErrorPath</key>
<string>$CONFIG_DIR/stderr.log</string>
</dict>
</plist>
EOF
# Load service
launchctl unload "$plist_file" 2>/dev/null || true
launchctl load "$plist_file"
print_ok "LaunchAgent created and loaded"
print_info "Logs: tail -f $CONFIG_DIR/stderr.log"
}
create_systemd_service() {
local service_file="$HOME/.config/systemd/user/messenger-imessage.service"
local backend_dir="$SCRIPT_DIR/imessage-sync/backend"
mkdir -p "$(dirname "$service_file")"
cat > "$service_file" <<EOF
[Unit]
Description=Messenger iMessage Sync Service
After=network.target
[Service]
Type=simple
WorkingDirectory=$backend_dir
EnvironmentFile=$ENV_FILE
ExecStart=$(which node) dist/main.js
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable messenger-imessage
systemctl --user start messenger-imessage
print_ok "systemd service created and started"
print_info "Logs: journalctl --user -u messenger-imessage -f"
}
# ─── Verify ──────────────────────────────────────────────────────────────────
verify_install() {
print_step "Verifying installation..."
sleep 3
local health_url="http://localhost:$SERVICE_PORT/health"
local response
response=$(curl -s --connect-timeout 5 "$health_url" 2>/dev/null || echo "")
if [[ -n "$response" ]]; then
print_ok "Service is running at http://localhost:$SERVICE_PORT"
else
print_warn "Service not responding yet (may still be starting)"
print_info "Check logs for details"
fi
}
# ─── Main ────────────────────────────────────────────────────────────────────
main() {
print_header
# Interactive if no mode specified
if [[ -z "$MODE" ]]; then
interactive_setup
fi
echo ""
print_info "Mode: $MODE"
print_info "Port: $SERVICE_PORT"
[[ -n "$LIFE_MANAGER_URL" ]] && print_info "Life Manager: $LIFE_MANAGER_URL"
echo ""
generate_env
setup_local_docker
install_backend
create_service
verify_install
echo ""
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN} Installation Complete${NC}"
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo "Config: $ENV_FILE"
echo "Service: http://localhost:$SERVICE_PORT"
[[ -n "$LIFE_MANAGER_URL" ]] && echo "Life Manager: $LIFE_MANAGER_URL"
echo ""
echo "Next steps:"
echo " 1. Install the macOS agent: cd imessage-macos && ./install.sh http://localhost:$SERVICE_PORT"
echo " 2. Grant Full Disk Access to the agent"
echo ""
}
main