diff --git a/tools/deploy.sh b/tools/deploy.sh new file mode 100755 index 0000000..04af30a --- /dev/null +++ b/tools/deploy.sh @@ -0,0 +1,124 @@ +#!/bin/bash +set -e + +REMOTE_HOST="memory" +REMOTE_DIR="/home/ec2-user/memory" +DEFAULT_BRANCH="master" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +usage() { + echo "Usage: $0 [options]" + echo "" + echo "Commands:" + echo " sync Rsync local code to server" + echo " pull [branch] Git checkout and pull (default: master)" + echo " restart Restart docker services" + echo " deploy [branch] Pull + restart" + echo " run Run command on server (with venv activated)" + exit 1 +} + +sync_code() { + echo -e "${GREEN}Syncing code to $REMOTE_HOST...${NC}" + + rsync -avz --delete \ + --exclude='__pycache__' \ + --exclude='*.pyc' \ + --exclude='*.pyo' \ + --exclude='.git' \ + --exclude='memory_files' \ + --exclude='secrets' \ + --exclude='Books' \ + --exclude='clean_books' \ + --exclude='.env' \ + --exclude='venv' \ + --exclude='.venv' \ + --exclude='*.egg-info' \ + --exclude='node_modules' \ + --exclude='.DS_Store' \ + --exclude='docker-compose.override.yml' \ + --exclude='.pytest_cache' \ + --exclude='.mypy_cache' \ + --exclude='.ruff_cache' \ + --exclude='htmlcov' \ + --exclude='.coverage' \ + --exclude='*.log' \ + "$PROJECT_DIR/src" \ + "$PROJECT_DIR/tests" \ + "$PROJECT_DIR/tools" \ + "$PROJECT_DIR/db" \ + "$PROJECT_DIR/docker" \ + "$PROJECT_DIR/frontend" \ + "$PROJECT_DIR/requirements" \ + "$PROJECT_DIR/setup.py" \ + "$PROJECT_DIR/docker-compose.yaml" \ + "$PROJECT_DIR/pytest.ini" \ + "$REMOTE_HOST:$REMOTE_DIR/" + + echo -e "${GREEN}Sync complete!${NC}" +} + +git_pull() { + local branch="${1:-$DEFAULT_BRANCH}" + echo -e "${GREEN}Pulling branch '$branch' on $REMOTE_HOST...${NC}" + + ssh "$REMOTE_HOST" "cd $REMOTE_DIR && \ + git stash --quiet 2>/dev/null || true && \ + git fetch origin && \ + git checkout $branch && \ + git pull origin $branch" + + echo -e "${GREEN}Pull complete!${NC}" +} + +restart_services() { + echo -e "${GREEN}Restarting services on $REMOTE_HOST...${NC}" + + ssh "$REMOTE_HOST" "cd $REMOTE_DIR && docker compose up --build -d" + + echo -e "${GREEN}Services restarted!${NC}" +} + +deploy() { + local branch="${1:-$DEFAULT_BRANCH}" + git_pull "$branch" + restart_services +} + +run_remote() { + if [ $# -eq 0 ]; then + echo -e "${RED}Error: No command specified${NC}" + exit 1 + fi + ssh "$REMOTE_HOST" "cd $REMOTE_DIR && source venv/bin/activate && $*" +} + +# Main +case "${1:-}" in + sync) + sync_code + ;; + pull) + git_pull "${2:-}" + ;; + restart) + restart_services + ;; + deploy) + deploy "${2:-}" + ;; + run) + shift + run_remote "$@" + ;; + *) + usage + ;; +esac diff --git a/tools/diagnose.sh b/tools/diagnose.sh new file mode 100755 index 0000000..4328cbf --- /dev/null +++ b/tools/diagnose.sh @@ -0,0 +1,195 @@ +#!/bin/bash +set -e + +REMOTE_HOST="memory" +REMOTE_DIR="/home/ec2-user/memory" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +usage() { + echo "Usage: $0 [options]" + echo "" + echo "Safe diagnostic commands for the memory server." + echo "" + echo "Commands:" + echo " logs [service] [lines] View docker logs (default: all services, 100 lines)" + echo " ps Show docker container status" + echo " disk Show disk usage" + echo " mem Show memory usage" + echo " top Show running processes" + echo " ls List directory contents" + echo " cat View file contents" + echo " tail [lines] Tail a file (default: 50 lines)" + echo " grep Search for pattern in files" + echo " db Run read-only SQL query" + echo " get [port] GET request to localhost (default port: 8000)" + echo " status Overall system status" + exit 1 +} + +remote() { + ssh "$REMOTE_HOST" "$@" +} + +docker_logs() { + local service="${1:-}" + local lines="${2:-100}" + if [ -n "$service" ]; then + echo -e "${GREEN}Logs for $service (last $lines lines):${NC}" + remote "cd $REMOTE_DIR && docker compose logs --tail=$lines $service" + else + echo -e "${GREEN}All logs (last $lines lines):${NC}" + remote "cd $REMOTE_DIR && docker compose logs --tail=$lines" + fi +} + +docker_ps() { + echo -e "${GREEN}Container status:${NC}" + remote "cd $REMOTE_DIR && docker compose ps" +} + +disk_usage() { + echo -e "${GREEN}Disk usage:${NC}" + remote "df -h && echo '' && du -sh $REMOTE_DIR/* 2>/dev/null | sort -h" +} + +mem_usage() { + echo -e "${GREEN}Memory usage:${NC}" + remote "free -h" +} + +show_top() { + echo -e "${GREEN}Top processes:${NC}" + remote "ps aux --sort=-%mem | head -20" +} + +list_dir() { + local path="${1:-.}" + # Ensure path is within project directory for safety + echo -e "${GREEN}Contents of $path:${NC}" + remote "cd $REMOTE_DIR && ls -la $path" +} + +cat_file() { + local file="$1" + if [ -z "$file" ]; then + echo -e "${RED}Error: No file specified${NC}" + exit 1 + fi + echo -e "${GREEN}Contents of $file:${NC}" + remote "cd $REMOTE_DIR && cat $file" +} + +tail_file() { + local file="$1" + local lines="${2:-50}" + if [ -z "$file" ]; then + echo -e "${RED}Error: No file specified${NC}" + exit 1 + fi + echo -e "${GREEN}Last $lines lines of $file:${NC}" + remote "cd $REMOTE_DIR && tail -n $lines $file" +} + +grep_files() { + local pattern="$1" + local path="${2:-.}" + if [ -z "$pattern" ]; then + echo -e "${RED}Error: No pattern specified${NC}" + exit 1 + fi + echo -e "${GREEN}Searching for '$pattern' in $path:${NC}" + remote "cd $REMOTE_DIR && grep -r --color=always '$pattern' $path || true" +} + +db_query() { + local query="$1" + if [ -z "$query" ]; then + echo -e "${RED}Error: No query specified${NC}" + exit 1 + fi + # Only allow SELECT queries for safety + if ! echo "$query" | grep -qi "^select"; then + echo -e "${RED}Error: Only SELECT queries are allowed${NC}" + exit 1 + fi + echo -e "${GREEN}Running query:${NC}" + remote "cd $REMOTE_DIR && docker compose exec -T postgres psql -U kb -d kb -c \"$query\"" +} + +http_get() { + local path="$1" + local port="${2:-8000}" + if [ -z "$path" ]; then + echo -e "${RED}Error: No path specified${NC}" + exit 1 + fi + # Ensure path starts with / + if [[ "$path" != /* ]]; then + path="/$path" + fi + echo -e "${GREEN}GET http://localhost:${port}${path}${NC}" + remote "curl -s -w '\n\nHTTP Status: %{http_code}\n' 'http://localhost:${port}${path}'" +} + +system_status() { + echo -e "${GREEN}=== System Status ===${NC}" + echo "" + docker_ps + echo "" + echo -e "${GREEN}=== Memory ===${NC}" + mem_usage + echo "" + echo -e "${GREEN}=== Disk ===${NC}" + remote "df -h /" + echo "" + echo -e "${GREEN}=== Recent Errors (last 20 lines) ===${NC}" + remote "cd $REMOTE_DIR && docker compose logs --tail=100 2>&1 | grep -i -E '(error|exception|failed|fatal)' | tail -20 || echo 'No recent errors found'" +} + +# Main +case "${1:-}" in + logs) + docker_logs "${2:-}" "${3:-}" + ;; + ps) + docker_ps + ;; + disk) + disk_usage + ;; + mem) + mem_usage + ;; + top) + show_top + ;; + ls) + list_dir "${2:-}" + ;; + cat) + cat_file "${2:-}" + ;; + tail) + tail_file "${2:-}" "${3:-}" + ;; + grep) + grep_files "${2:-}" "${3:-}" + ;; + db) + db_query "${2:-}" + ;; + get) + http_get "${2:-}" "${3:-}" + ;; + status) + system_status + ;; + *) + usage + ;; +esac