feat(mac-mini): add stability improvements

High priority stability features:
- Add all LaunchD plists to Git for version control
- Handle crash-looping containers (Restarting status) in ensure-containers.sh
- Add database backup script with daily/weekly rotation
- Add Docker log rotation setup (50MB max, 3 files per container)

New files:
- scripts/mac-mini/backup-databases.sh - Daily pg_dump with rotation
- scripts/mac-mini/setup-docker-logging.sh - Configure daemon.json
- scripts/mac-mini/launchd/*.plist - All 8 LaunchD service configs
- scripts/mac-mini/launchd/README.md - Documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2026-02-12 13:33:44 +01:00
parent 7d409465b6
commit 3de2f25552
12 changed files with 615 additions and 23 deletions

View file

@ -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 "🚨 <b>Backup Failed</b>\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 "⚠️ <b>Backup Partially Failed</b>\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 "💾 <b>Weekly Backup Complete</b>\n\n$BACKUP_COUNT databases backed up\nTotal size: $TOTAL_SIZE"
fi
fi

View file

@ -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 "🚨 <b>Container needs manual fix</b>\n\n$container has crashed $ATTEMPTS times. Check logs:\n<code>docker logs $container</code>" "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 "🔧 <b>ManaCore Auto-Recovery</b>\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 "⚠️ <b>ManaCore Container Issue</b>\n\nContainers still stuck: $(echo $STILL_STUCK | tr '\n' ', ')"
send_notification "⚠️ <b>ManaCore Container Issue</b>\n\nContainers still broken: $(echo $ALL_STILL_BROKEN | tr '\n' ', ')" "high"
exit 1
fi

View file

@ -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.<service>.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-<service>.error.log
```

View file

@ -0,0 +1,35 @@
<?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.manacore.backup-databases</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/mana/projects/manacore-monorepo/scripts/mac-mini/backup-databases.sh</string>
</array>
<!-- Run daily at 3:00 AM -->
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>3</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>/tmp/manacore-backup.log</string>
<key>StandardErrorPath</key>
<string>/tmp/manacore-backup.error.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
</dict>
</dict>
</plist>

View file

@ -0,0 +1,26 @@
<?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.manacore.docker-startup</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/mana/projects/manacore-monorepo/scripts/mac-mini/startup.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StartInterval</key>
<integer>0</integer>
<key>StandardOutPath</key>
<string>/tmp/manacore-startup.log</string>
<key>StandardErrorPath</key>
<string>/tmp/manacore-startup.error.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
</dict>
</plist>

View file

@ -0,0 +1,24 @@
<?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.manacore.health-check</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/mana/projects/manacore-monorepo/scripts/mac-mini/health-check.sh</string>
</array>
<key>StartInterval</key>
<integer>300</integer>
<key>StandardOutPath</key>
<string>/tmp/manacore-health.log</string>
<key>StandardErrorPath</key>
<string>/tmp/manacore-health.error.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
</dict>
</plist>

View file

@ -0,0 +1,53 @@
<?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.manacore.image-gen</string>
<key>ProgramArguments</key>
<array>
<string>/Users/mana/projects/manacore-monorepo/services/mana-image-gen/.venv/bin/python3</string>
<string>-m</string>
<string>uvicorn</string>
<string>app.main:app</string>
<string>--host</string>
<string>0.0.0.0</string>
<string>--port</string>
<string>3025</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/mana/projects/manacore-monorepo/services/mana-image-gen</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/Users/mana/projects/manacore-monorepo/services/mana-image-gen/.venv/bin:/usr/local/bin:/usr/bin:/bin</string>
<key>HOME</key>
<string>/Users/mana</string>
<key>PORT</key>
<string>3025</string>
<key>FLUX_BINARY</key>
<string>/Users/mana/flux2/flux</string>
<key>FLUX_MODEL_DIR</key>
<string>/Users/mana/flux2/model</string>
<key>DEFAULT_STEPS</key>
<string>4</string>
<key>GENERATION_TIMEOUT</key>
<string>300</string>
<key>CORS_ORIGINS</key>
<string>https://mana.how,https://picture.mana.how,https://chat.mana.how</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
<key>Crashed</key>
<true/>
</dict>
<key>StandardOutPath</key>
<string>/tmp/manacore-image-gen.log</string>
<key>StandardErrorPath</key>
<string>/tmp/manacore-image-gen.error.log</string>
</dict>
</plist>

View file

@ -0,0 +1,39 @@
<?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.manacore.mana-stt</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>cd /Users/mana/projects/manacore-monorepo/services/mana-stt &amp;&amp; set -a &amp;&amp; source .env &amp;&amp; set +a &amp;&amp; .venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 3020</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/mana/projects/manacore-monorepo/services/mana-stt</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/mana/logs/mana-stt.log</string>
<key>StandardErrorPath</key>
<string>/Users/mana/logs/mana-stt.error.log</string>
<key>ThrottleInterval</key>
<integer>10</integer>
</dict>
</plist>

View file

@ -0,0 +1,39 @@
<?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.manacore.mana-tts</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>cd /Users/mana/projects/manacore-monorepo/services/mana-tts &amp;&amp; set -a &amp;&amp; source .env &amp;&amp; set +a &amp;&amp; .venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 3022</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/mana/projects/manacore-monorepo/services/mana-tts</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/mana/logs/mana-tts.log</string>
<key>StandardErrorPath</key>
<string>/Users/mana/logs/mana-tts.error.log</string>
<key>ThrottleInterval</key>
<integer>10</integer>
</dict>
</plist>

View file

@ -0,0 +1,18 @@
<?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.manacore.ssd-check</string>
<key>ProgramArguments</key>
<array>
<string>/Users/mana/check-ssd.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/mana/Library/Logs/ssd-check.log</string>
<key>StandardErrorPath</key>
<string>/Users/mana/Library/Logs/ssd-check.log</string>
</dict>
</plist>

View file

@ -0,0 +1,36 @@
<?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.manacore.telegram-ollama-bot</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/node</string>
<string>/Users/mana/projects/manacore-monorepo/services/telegram-ollama-bot/dist/main.js</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/mana/projects/manacore-monorepo/services/telegram-ollama-bot</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
<key>TELEGRAM_BOT_TOKEN</key>
<string>8559479868:AAHF3g7vYLs0eOvDLh7hFVnIB-V8CKehUOM</string>
<key>OLLAMA_URL</key>
<string>http://localhost:11434</string>
<key>OLLAMA_MODEL</key>
<string>gemma3:4b</string>
<key>PORT</key>
<string>3301</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/mana/Library/Logs/telegram-ollama-bot.log</string>
<key>StandardErrorPath</key>
<string>/Users/mana/Library/Logs/telegram-ollama-bot.log</string>
</dict>
</plist>

View file

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