diff --git a/scripts/mac-mini/backup-databases.sh b/scripts/mac-mini/backup-databases.sh
new file mode 100755
index 000000000..95b6b6662
--- /dev/null
+++ b/scripts/mac-mini/backup-databases.sh
@@ -0,0 +1,125 @@
+#!/bin/bash
+# ManaCore Database Backup Script
+# Creates daily backups of all PostgreSQL databases with rotation
+#
+# Retention policy:
+# - Daily backups: keep last 7 days
+# - Weekly backups: keep last 4 weeks (Sundays)
+#
+# Run via LaunchD daily at 3 AM
+
+set -e
+
+# Ensure PATH includes docker
+export PATH="/usr/local/bin:/opt/homebrew/bin:$PATH"
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
+BACKUP_DIR="/Volumes/ManaData/backups/postgres"
+LOG_FILE="/tmp/manacore-backup.log"
+DATE=$(date +%Y-%m-%d)
+DAY_OF_WEEK=$(date +%u) # 1=Monday, 7=Sunday
+
+# Load env for password
+if [ -f "$PROJECT_ROOT/.env.macmini" ]; then
+ source "$PROJECT_ROOT/.env.macmini"
+fi
+
+POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-mana123}"
+
+log() {
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
+}
+
+# Load notification config if exists
+if [ -f "$PROJECT_ROOT/.env.notifications" ]; then
+ source "$PROJECT_ROOT/.env.notifications"
+fi
+
+send_notification() {
+ local message="$1"
+ local priority="${2:-default}"
+
+ if [ -n "$TELEGRAM_BOT_TOKEN" ] && [ -n "$TELEGRAM_CHAT_ID" ]; then
+ curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
+ -d "chat_id=${TELEGRAM_CHAT_ID}" \
+ -d "text=${message}" \
+ -d "parse_mode=HTML" \
+ >/dev/null 2>&1 || true
+ fi
+}
+
+# Create backup directories
+mkdir -p "$BACKUP_DIR/daily"
+mkdir -p "$BACKUP_DIR/weekly"
+
+log "=== ManaCore Database Backup ==="
+
+# Check if postgres container is running
+if ! docker ps --format '{{.Names}}' | grep -q "mana-infra-postgres"; then
+ log "ERROR: PostgreSQL container is not running"
+ send_notification "🚨 Backup Failed\n\nPostgreSQL container not running" "high"
+ exit 1
+fi
+
+# Get list of databases (exclude templates and postgres)
+DATABASES=$(docker exec mana-infra-postgres psql -U postgres -t -c "SELECT datname FROM pg_database WHERE datistemplate = false AND datname != 'postgres';" | tr -d ' ' | grep -v "^$")
+
+log "Found databases: $(echo $DATABASES | tr '\n' ' ')"
+
+BACKUP_COUNT=0
+BACKUP_SIZE=0
+FAILED_DBS=""
+
+for DB in $DATABASES; do
+ log "Backing up: $DB"
+ BACKUP_FILE="$BACKUP_DIR/daily/${DB}_${DATE}.sql.gz"
+
+ # Create backup using pg_dump inside container, compress with gzip
+ if docker exec mana-infra-postgres pg_dump -U postgres "$DB" 2>/dev/null | gzip > "$BACKUP_FILE"; then
+ SIZE=$(ls -lh "$BACKUP_FILE" | awk '{print $5}')
+ log " OK: $DB ($SIZE)"
+ BACKUP_COUNT=$((BACKUP_COUNT + 1))
+ BACKUP_SIZE=$((BACKUP_SIZE + $(stat -f%z "$BACKUP_FILE" 2>/dev/null || stat -c%s "$BACKUP_FILE" 2>/dev/null)))
+ else
+ log " FAILED: $DB"
+ FAILED_DBS="$FAILED_DBS $DB"
+ rm -f "$BACKUP_FILE" # Remove incomplete backup
+ fi
+done
+
+# On Sunday, create weekly backup
+if [ "$DAY_OF_WEEK" -eq 7 ]; then
+ log "Creating weekly backup (Sunday)..."
+ WEEKLY_DIR="$BACKUP_DIR/weekly/$DATE"
+ mkdir -p "$WEEKLY_DIR"
+ cp "$BACKUP_DIR/daily/"*"_${DATE}.sql.gz" "$WEEKLY_DIR/" 2>/dev/null || true
+ log "Weekly backup created in $WEEKLY_DIR"
+fi
+
+# Rotate daily backups (keep last 7 days)
+log "Rotating daily backups (keeping 7 days)..."
+find "$BACKUP_DIR/daily" -name "*.sql.gz" -mtime +7 -delete 2>/dev/null || true
+
+# Rotate weekly backups (keep last 4 weeks)
+log "Rotating weekly backups (keeping 4 weeks)..."
+find "$BACKUP_DIR/weekly" -mindepth 1 -maxdepth 1 -type d -mtime +28 -exec rm -rf {} \; 2>/dev/null || true
+
+# Calculate total backup size
+TOTAL_SIZE=$(du -sh "$BACKUP_DIR" 2>/dev/null | awk '{print $1}')
+
+log "=== Backup Summary ==="
+log "Databases backed up: $BACKUP_COUNT"
+log "Total backup size: $TOTAL_SIZE"
+
+if [ -n "$FAILED_DBS" ]; then
+ log "FAILED databases:$FAILED_DBS"
+ send_notification "⚠️ Backup Partially Failed\n\nFailed:$FAILED_DBS\nSuccessful: $BACKUP_COUNT databases" "high"
+ exit 1
+else
+ log "All backups successful!"
+ # Only send notification on Sundays (weekly summary)
+ if [ "$DAY_OF_WEEK" -eq 7 ]; then
+ send_notification "💾 Weekly Backup Complete\n\n$BACKUP_COUNT databases backed up\nTotal size: $TOTAL_SIZE"
+ fi
+fi
diff --git a/scripts/mac-mini/ensure-containers-running.sh b/scripts/mac-mini/ensure-containers-running.sh
index aba49db94..f2711d46c 100755
--- a/scripts/mac-mini/ensure-containers-running.sh
+++ b/scripts/mac-mini/ensure-containers-running.sh
@@ -1,9 +1,10 @@
#!/bin/bash
# ManaCore Container Health Enforcer
-# Ensures all containers are actually running, not just created
+# Ensures all containers are actually running and healthy
#
-# This script detects containers that are stuck in "Created" or "Exited"
-# status and automatically starts them.
+# This script detects containers that are:
+# - Stuck in "Created" or "Exited" status -> starts them
+# - Crash-looping in "Restarting" status -> recreates them
#
# Run via LaunchD every 5 minutes or after system startup.
@@ -17,6 +18,7 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
COMPOSE_FILE="$PROJECT_ROOT/docker-compose.macmini.yml"
ENV_FILE="$PROJECT_ROOT/.env.macmini"
LOG_FILE="/tmp/manacore-container-health.log"
+RESTART_TRACKER="/tmp/manacore-restart-tracker"
# Load notification config if exists
if [ -f "$PROJECT_ROOT/.env.notifications" ]; then
@@ -29,6 +31,7 @@ log() {
send_notification() {
local message="$1"
+ local priority="${2:-default}"
# Telegram
if [ -n "$TELEGRAM_BOT_TOKEN" ] && [ -n "$TELEGRAM_CHAT_ID" ]; then
@@ -42,8 +45,8 @@ send_notification() {
# ntfy
if [ -n "$NTFY_TOPIC" ]; then
curl -s -d "$message" \
- -H "Title: ManaCore Container Fixed" \
- -H "Priority: default" \
+ -H "Title: ManaCore Container Health" \
+ -H "Priority: $priority" \
-H "Tags: white_check_mark" \
"https://ntfy.sh/$NTFY_TOPIC" >/dev/null 2>&1 || true
fi
@@ -55,29 +58,90 @@ if ! docker info >/dev/null 2>&1; then
exit 1
fi
-# Get containers that are NOT running (Created, Exited, etc.)
-# Filter only mana-* containers from our compose file
+# Get containers that are NOT running (Created, Exited)
STUCK_CONTAINERS=$(docker ps -a --filter "status=created" --filter "status=exited" --format "{{.Names}}" | grep "^mana-" || true)
-if [ -z "$STUCK_CONTAINERS" ]; then
+# Get containers that are crash-looping (Restarting)
+CRASHLOOP_CONTAINERS=$(docker ps -a --filter "status=restarting" --format "{{.Names}}" | grep "^mana-" || true)
+
+# Track restart attempts to avoid infinite loops
+track_restart() {
+ local container="$1"
+ local count_file="$RESTART_TRACKER/$container"
+ mkdir -p "$RESTART_TRACKER"
+
+ if [ -f "$count_file" ]; then
+ local count=$(cat "$count_file")
+ local age=$(( $(date +%s) - $(stat -f %m "$count_file" 2>/dev/null || stat -c %Y "$count_file" 2>/dev/null) ))
+ # Reset counter if more than 1 hour old
+ if [ "$age" -gt 3600 ]; then
+ echo "1" > "$count_file"
+ echo "1"
+ else
+ count=$((count + 1))
+ echo "$count" > "$count_file"
+ echo "$count"
+ fi
+ else
+ echo "1" > "$count_file"
+ echo "1"
+ fi
+}
+
+if [ -z "$STUCK_CONTAINERS" ] && [ -z "$CRASHLOOP_CONTAINERS" ]; then
log "OK: All containers are running"
exit 0
fi
-log "WARNING: Found containers not running:"
-echo "$STUCK_CONTAINERS" | while read container; do
- STATUS=$(docker inspect "$container" --format '{{.State.Status}}' 2>/dev/null || echo "unknown")
- log " - $container (status: $STATUS)"
-done
+# Handle crash-looping containers first (more critical)
+if [ -n "$CRASHLOOP_CONTAINERS" ]; then
+ log "WARNING: Found crash-looping containers:"
+ for container in $CRASHLOOP_CONTAINERS; do
+ RESTART_COUNT=$(docker inspect "$container" --format '{{.RestartCount}}' 2>/dev/null || echo "0")
+ log " - $container (restart count: $RESTART_COUNT)"
+ done
-# Start the stuck containers using docker compose
-log "Starting stuck containers via docker compose..."
+ log "Attempting to recover crash-looping containers..."
+ for container in $CRASHLOOP_CONTAINERS; do
+ ATTEMPTS=$(track_restart "$container")
+
+ if [ "$ATTEMPTS" -gt 3 ]; then
+ log " SKIP: $container has been restarted $ATTEMPTS times in the last hour, needs manual intervention"
+ send_notification "🚨 Container needs manual fix\n\n$container has crashed $ATTEMPTS times. Check logs:\ndocker logs $container" "high"
+ continue
+ fi
+
+ log " Recreating $container (attempt $ATTEMPTS/3)..."
+ # Stop, remove and recreate the container
+ docker stop "$container" 2>/dev/null || true
+ docker rm "$container" 2>/dev/null || true
+ done
+fi
+
+# Handle stuck containers (Created/Exited)
+if [ -n "$STUCK_CONTAINERS" ]; then
+ log "WARNING: Found containers not running:"
+ for container in $STUCK_CONTAINERS; do
+ STATUS=$(docker inspect "$container" --format '{{.State.Status}}' 2>/dev/null || echo "unknown")
+ log " - $container (status: $STATUS)"
+ done
+fi
+
+# Combine all containers that need to be started
+ALL_PROBLEM_CONTAINERS=$(echo -e "$STUCK_CONTAINERS\n$CRASHLOOP_CONTAINERS" | grep -v "^$" | sort -u || true)
+
+if [ -z "$ALL_PROBLEM_CONTAINERS" ]; then
+ log "OK: No containers need recovery"
+ exit 0
+fi
+
+log "Starting containers via docker compose..."
cd "$PROJECT_ROOT"
# Use docker compose up for the specific services
# This ensures dependencies are respected
-for container in $STUCK_CONTAINERS; do
+for container in $ALL_PROBLEM_CONTAINERS; do
# Extract service name from container name (remove mana-app- or mana-* prefix)
# Container naming: mana-{category}-{service} or mana-app-{service}-{type}
SERVICE_NAME=""
@@ -139,18 +203,21 @@ done
# Wait for containers to start
sleep 10
-# Verify containers are now running
+# Verify containers are now running (check for created, exited, AND restarting)
STILL_STUCK=$(docker ps -a --filter "status=created" --filter "status=exited" --format "{{.Names}}" | grep "^mana-" || true)
+STILL_CRASHING=$(docker ps -a --filter "status=restarting" --format "{{.Names}}" | grep "^mana-" || true)
+ALL_STILL_BROKEN=$(echo -e "$STILL_STUCK\n$STILL_CRASHING" | grep -v "^$" | sort -u || true)
-if [ -z "$STILL_STUCK" ]; then
- FIXED_MSG="Auto-fixed stuck containers: $(echo $STUCK_CONTAINERS | tr '\n' ', ')"
+if [ -z "$ALL_STILL_BROKEN" ]; then
+ FIXED_MSG="Auto-fixed containers: $(echo $ALL_PROBLEM_CONTAINERS | tr '\n' ', ')"
log "SUCCESS: $FIXED_MSG"
send_notification "🔧 ManaCore Auto-Recovery\n\n$FIXED_MSG"
else
- log "ERROR: Some containers still not running:"
- echo "$STILL_STUCK" | while read container; do
- log " - $container"
+ log "ERROR: Some containers still have issues:"
+ for container in $ALL_STILL_BROKEN; do
+ STATUS=$(docker inspect "$container" --format '{{.State.Status}}' 2>/dev/null || echo "unknown")
+ log " - $container (status: $STATUS)"
done
- send_notification "⚠️ ManaCore Container Issue\n\nContainers still stuck: $(echo $STILL_STUCK | tr '\n' ', ')"
+ send_notification "⚠️ ManaCore Container Issue\n\nContainers still broken: $(echo $ALL_STILL_BROKEN | tr '\n' ', ')" "high"
exit 1
fi
diff --git a/scripts/mac-mini/launchd/README.md b/scripts/mac-mini/launchd/README.md
new file mode 100644
index 000000000..57a4b26dc
--- /dev/null
+++ b/scripts/mac-mini/launchd/README.md
@@ -0,0 +1,56 @@
+# LaunchD Services for Mac Mini
+
+These plist files configure automatic services on the Mac Mini server.
+
+## Installation
+
+```bash
+# Copy all plists to LaunchAgents
+cp *.plist ~/Library/LaunchAgents/
+
+# Load all services
+for f in *.plist; do launchctl load ~/Library/LaunchAgents/$f; done
+```
+
+## Services
+
+| Service | Description | Interval |
+|---------|-------------|----------|
+| `docker-startup` | Starts Docker containers on boot | At login |
+| `ensure-containers` | Detects and restarts stuck containers | Every 5 min |
+| `health-check` | Checks all services and sends alerts | Every 5 min |
+| `ssd-check` | Monitors SSD health | Periodic |
+| `mana-stt` | Speech-to-text service (Whisper) | At login |
+| `mana-tts` | Text-to-speech service (Kokoro) | At login |
+| `image-gen` | Image generation service | At login |
+| `telegram-ollama-bot` | Telegram bot with Ollama | At login |
+
+## Management Commands
+
+```bash
+# Check status
+launchctl list | grep manacore
+
+# View logs
+tail -f /tmp/manacore-*.log
+
+# Reload a service
+launchctl unload ~/Library/LaunchAgents/com.manacore.health-check.plist
+launchctl load ~/Library/LaunchAgents/com.manacore.health-check.plist
+
+# Stop a service
+launchctl unload ~/Library/LaunchAgents/com.manacore..plist
+```
+
+## Troubleshooting
+
+Exit codes in `launchctl list`:
+- `0` = Running successfully
+- `1` = Last run had errors (check logs)
+- `-` = Not running / waiting for next interval
+- `78` = Configuration error
+
+Check error logs:
+```bash
+cat /tmp/manacore-.error.log
+```
diff --git a/scripts/mac-mini/launchd/com.manacore.backup-databases.plist b/scripts/mac-mini/launchd/com.manacore.backup-databases.plist
new file mode 100644
index 000000000..98ccf8d0f
--- /dev/null
+++ b/scripts/mac-mini/launchd/com.manacore.backup-databases.plist
@@ -0,0 +1,35 @@
+
+
+
+
+ Label
+ com.manacore.backup-databases
+
+ ProgramArguments
+
+ /bin/bash
+ /Users/mana/projects/manacore-monorepo/scripts/mac-mini/backup-databases.sh
+
+
+
+ StartCalendarInterval
+
+ Hour
+ 3
+ Minute
+ 0
+
+
+ StandardOutPath
+ /tmp/manacore-backup.log
+
+ StandardErrorPath
+ /tmp/manacore-backup.error.log
+
+ EnvironmentVariables
+
+ PATH
+ /usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin
+
+
+
diff --git a/scripts/mac-mini/launchd/com.manacore.docker-startup.plist b/scripts/mac-mini/launchd/com.manacore.docker-startup.plist
new file mode 100644
index 000000000..54645cf09
--- /dev/null
+++ b/scripts/mac-mini/launchd/com.manacore.docker-startup.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ Label
+ com.manacore.docker-startup
+ ProgramArguments
+
+ /bin/bash
+ /Users/mana/projects/manacore-monorepo/scripts/mac-mini/startup.sh
+
+ RunAtLoad
+
+ StartInterval
+ 0
+ StandardOutPath
+ /tmp/manacore-startup.log
+ StandardErrorPath
+ /tmp/manacore-startup.error.log
+ EnvironmentVariables
+
+ PATH
+ /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
+
+
+
diff --git a/scripts/mac-mini/launchd/com.manacore.health-check.plist b/scripts/mac-mini/launchd/com.manacore.health-check.plist
new file mode 100644
index 000000000..674034773
--- /dev/null
+++ b/scripts/mac-mini/launchd/com.manacore.health-check.plist
@@ -0,0 +1,24 @@
+
+
+
+
+ Label
+ com.manacore.health-check
+ ProgramArguments
+
+ /bin/bash
+ /Users/mana/projects/manacore-monorepo/scripts/mac-mini/health-check.sh
+
+ StartInterval
+ 300
+ StandardOutPath
+ /tmp/manacore-health.log
+ StandardErrorPath
+ /tmp/manacore-health.error.log
+ EnvironmentVariables
+
+ PATH
+ /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
+
+
+
diff --git a/scripts/mac-mini/launchd/com.manacore.image-gen.plist b/scripts/mac-mini/launchd/com.manacore.image-gen.plist
new file mode 100644
index 000000000..15a06d090
--- /dev/null
+++ b/scripts/mac-mini/launchd/com.manacore.image-gen.plist
@@ -0,0 +1,53 @@
+
+
+
+
+ Label
+ com.manacore.image-gen
+ ProgramArguments
+
+ /Users/mana/projects/manacore-monorepo/services/mana-image-gen/.venv/bin/python3
+ -m
+ uvicorn
+ app.main:app
+ --host
+ 0.0.0.0
+ --port
+ 3025
+
+ WorkingDirectory
+ /Users/mana/projects/manacore-monorepo/services/mana-image-gen
+ EnvironmentVariables
+
+ PATH
+ /opt/homebrew/bin:/Users/mana/projects/manacore-monorepo/services/mana-image-gen/.venv/bin:/usr/local/bin:/usr/bin:/bin
+ HOME
+ /Users/mana
+ PORT
+ 3025
+ FLUX_BINARY
+ /Users/mana/flux2/flux
+ FLUX_MODEL_DIR
+ /Users/mana/flux2/model
+ DEFAULT_STEPS
+ 4
+ GENERATION_TIMEOUT
+ 300
+ CORS_ORIGINS
+ https://mana.how,https://picture.mana.how,https://chat.mana.how
+
+ RunAtLoad
+
+ KeepAlive
+
+ SuccessfulExit
+
+ Crashed
+
+
+ StandardOutPath
+ /tmp/manacore-image-gen.log
+ StandardErrorPath
+ /tmp/manacore-image-gen.error.log
+
+
diff --git a/scripts/mac-mini/launchd/com.manacore.mana-stt.plist b/scripts/mac-mini/launchd/com.manacore.mana-stt.plist
new file mode 100644
index 000000000..0517819c8
--- /dev/null
+++ b/scripts/mac-mini/launchd/com.manacore.mana-stt.plist
@@ -0,0 +1,39 @@
+
+
+
+
+ Label
+ com.manacore.mana-stt
+
+ ProgramArguments
+
+ /bin/bash
+ -c
+ cd /Users/mana/projects/manacore-monorepo/services/mana-stt && set -a && source .env && set +a && .venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 3020
+
+
+ WorkingDirectory
+ /Users/mana/projects/manacore-monorepo/services/mana-stt
+
+ EnvironmentVariables
+
+ PATH
+ /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin
+
+
+ RunAtLoad
+
+
+ KeepAlive
+
+
+ StandardOutPath
+ /Users/mana/logs/mana-stt.log
+
+ StandardErrorPath
+ /Users/mana/logs/mana-stt.error.log
+
+ ThrottleInterval
+ 10
+
+
diff --git a/scripts/mac-mini/launchd/com.manacore.mana-tts.plist b/scripts/mac-mini/launchd/com.manacore.mana-tts.plist
new file mode 100644
index 000000000..513f2ebe0
--- /dev/null
+++ b/scripts/mac-mini/launchd/com.manacore.mana-tts.plist
@@ -0,0 +1,39 @@
+
+
+
+
+ Label
+ com.manacore.mana-tts
+
+ ProgramArguments
+
+ /bin/bash
+ -c
+ cd /Users/mana/projects/manacore-monorepo/services/mana-tts && set -a && source .env && set +a && .venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 3022
+
+
+ WorkingDirectory
+ /Users/mana/projects/manacore-monorepo/services/mana-tts
+
+ EnvironmentVariables
+
+ PATH
+ /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin
+
+
+ RunAtLoad
+
+
+ KeepAlive
+
+
+ StandardOutPath
+ /Users/mana/logs/mana-tts.log
+
+ StandardErrorPath
+ /Users/mana/logs/mana-tts.error.log
+
+ ThrottleInterval
+ 10
+
+
diff --git a/scripts/mac-mini/launchd/com.manacore.ssd-check.plist b/scripts/mac-mini/launchd/com.manacore.ssd-check.plist
new file mode 100644
index 000000000..e5043f0e2
--- /dev/null
+++ b/scripts/mac-mini/launchd/com.manacore.ssd-check.plist
@@ -0,0 +1,18 @@
+
+
+
+
+ Label
+ com.manacore.ssd-check
+ ProgramArguments
+
+ /Users/mana/check-ssd.sh
+
+ RunAtLoad
+
+ StandardOutPath
+ /Users/mana/Library/Logs/ssd-check.log
+ StandardErrorPath
+ /Users/mana/Library/Logs/ssd-check.log
+
+
diff --git a/scripts/mac-mini/launchd/com.manacore.telegram-ollama-bot.plist b/scripts/mac-mini/launchd/com.manacore.telegram-ollama-bot.plist
new file mode 100644
index 000000000..d3b32df3a
--- /dev/null
+++ b/scripts/mac-mini/launchd/com.manacore.telegram-ollama-bot.plist
@@ -0,0 +1,36 @@
+
+
+
+
+ Label
+ com.manacore.telegram-ollama-bot
+ ProgramArguments
+
+ /opt/homebrew/bin/node
+ /Users/mana/projects/manacore-monorepo/services/telegram-ollama-bot/dist/main.js
+
+ WorkingDirectory
+ /Users/mana/projects/manacore-monorepo/services/telegram-ollama-bot
+ EnvironmentVariables
+
+ PATH
+ /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin
+ TELEGRAM_BOT_TOKEN
+ 8559479868:AAHF3g7vYLs0eOvDLh7hFVnIB-V8CKehUOM
+ OLLAMA_URL
+ http://localhost:11434
+ OLLAMA_MODEL
+ gemma3:4b
+ PORT
+ 3301
+
+ RunAtLoad
+
+ KeepAlive
+
+ StandardOutPath
+ /Users/mana/Library/Logs/telegram-ollama-bot.log
+ StandardErrorPath
+ /Users/mana/Library/Logs/telegram-ollama-bot.log
+
+
diff --git a/scripts/mac-mini/setup-docker-logging.sh b/scripts/mac-mini/setup-docker-logging.sh
new file mode 100755
index 000000000..42722bcfc
--- /dev/null
+++ b/scripts/mac-mini/setup-docker-logging.sh
@@ -0,0 +1,74 @@
+#!/bin/bash
+# Configure Docker daemon for log rotation
+# This prevents docker logs from consuming unlimited disk space
+#
+# Run once after Docker Desktop installation
+
+set -e
+
+DOCKER_CONFIG_DIR="$HOME/.docker"
+DAEMON_JSON="$DOCKER_CONFIG_DIR/daemon.json"
+
+echo "=== Docker Log Rotation Setup ==="
+
+# Create config directory if needed
+mkdir -p "$DOCKER_CONFIG_DIR"
+
+# Backup existing config
+if [ -f "$DAEMON_JSON" ]; then
+ cp "$DAEMON_JSON" "${DAEMON_JSON}.backup.$(date +%Y%m%d)"
+ echo "Backed up existing daemon.json"
+fi
+
+# Check if config exists and has content
+if [ -f "$DAEMON_JSON" ] && [ -s "$DAEMON_JSON" ]; then
+ # Merge with existing config using jq if available
+ if command -v jq &> /dev/null; then
+ echo "Merging with existing daemon.json..."
+ jq '. + {
+ "log-driver": "json-file",
+ "log-opts": {
+ "max-size": "50m",
+ "max-file": "3",
+ "compress": "true"
+ }
+ }' "$DAEMON_JSON" > "${DAEMON_JSON}.tmp" && mv "${DAEMON_JSON}.tmp" "$DAEMON_JSON"
+ else
+ echo "WARNING: jq not installed, cannot merge configs"
+ echo "Please manually add log settings to $DAEMON_JSON"
+ echo ""
+ echo 'Add this to your daemon.json:'
+ echo ' "log-driver": "json-file",'
+ echo ' "log-opts": {'
+ echo ' "max-size": "50m",'
+ echo ' "max-file": "3",'
+ echo ' "compress": "true"'
+ echo ' }'
+ exit 1
+ fi
+else
+ # Create new config
+ echo "Creating new daemon.json..."
+ cat > "$DAEMON_JSON" << 'EOF'
+{
+ "log-driver": "json-file",
+ "log-opts": {
+ "max-size": "50m",
+ "max-file": "3",
+ "compress": "true"
+ }
+}
+EOF
+fi
+
+echo ""
+echo "Log rotation configured:"
+echo " - Max size per log file: 50MB"
+echo " - Max files per container: 3"
+echo " - Compression: enabled"
+echo " - Total max logs per container: ~150MB"
+echo ""
+echo "IMPORTANT: Restart Docker Desktop for changes to take effect!"
+echo ""
+echo "To verify after restart:"
+echo " docker info | grep -A5 'Logging Driver'"