Add comprehensive Docker setup with PostgreSQL, Redis, ChromaDB, and Qdrant
- Enhanced install.py with Docker detection and automatic service setup - Added docker-compose.services.yml for standalone database services - Created docker-services.sh management script for easy service control - Added DOCKER.md documentation with complete setup instructions - Updated requirements.txt for Python 3.13 compatibility - Added multiple test scripts and configuration files - Enhanced collaborative creative projects with proper database integration - Fixed SQLAlchemy metadata field conflicts in database models - Added comprehensive quickstart and testing guides Services now available: - PostgreSQL with Docker - Redis with Docker - ChromaDB vector database - Qdrant vector database (recommended) - PgAdmin for database administration The setup script now automatically detects Docker and offers streamlined installation with one-command service deployment.
This commit is contained in:
230
install.py
230
install.py
@@ -24,6 +24,8 @@ class FishbowlSetup:
|
||||
|
||||
self.python_executable = None
|
||||
self.config = {}
|
||||
self.docker_available = False
|
||||
self.use_docker_services = False
|
||||
|
||||
def print_header(self):
|
||||
"""Print welcome header"""
|
||||
@@ -129,6 +131,18 @@ class FishbowlSetup:
|
||||
self.print_info("Please install Git from https://git-scm.com/")
|
||||
sys.exit(1)
|
||||
|
||||
# Check for Docker and Docker Compose
|
||||
self.docker_available = False
|
||||
try:
|
||||
docker_result = subprocess.run(["docker", "--version"], check=True, capture_output=True, text=True)
|
||||
compose_result = subprocess.run(["docker", "compose", "version"], check=True, capture_output=True, text=True)
|
||||
self.print_success(f"Docker found: {docker_result.stdout.strip()}")
|
||||
self.print_success(f"Docker Compose found: {compose_result.stdout.strip()}")
|
||||
self.docker_available = True
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
self.print_warning("Docker/Docker Compose not found.")
|
||||
self.print_info("Install Docker Desktop for easier PostgreSQL/Redis setup: https://docker.com/")
|
||||
|
||||
# Check for Node.js (optional, for frontend)
|
||||
try:
|
||||
result = subprocess.run(["node", "--version"], check=True, capture_output=True, text=True)
|
||||
@@ -160,13 +174,33 @@ class FishbowlSetup:
|
||||
|
||||
self.print_info("Creating virtual environment...")
|
||||
try:
|
||||
subprocess.run([sys.executable, "-m", "venv", str(self.venv_path)],
|
||||
check=True, capture_output=True)
|
||||
# Try with sys.executable first
|
||||
result = subprocess.run([sys.executable, "-m", "venv", str(self.venv_path)],
|
||||
check=True, capture_output=True, text=True)
|
||||
self.print_success("Virtual environment created successfully")
|
||||
self.python_executable = self.get_venv_python()
|
||||
except subprocess.CalledProcessError as e:
|
||||
self.print_error(f"Failed to create virtual environment: {e}")
|
||||
sys.exit(1)
|
||||
self.print_warning(f"Failed with {sys.executable}: {e}")
|
||||
|
||||
# Try with python3 as fallback
|
||||
try:
|
||||
self.print_info("Trying with python3...")
|
||||
result = subprocess.run(["python3", "-m", "venv", str(self.venv_path)],
|
||||
check=True, capture_output=True, text=True)
|
||||
self.print_success("Virtual environment created successfully")
|
||||
self.python_executable = self.get_venv_python()
|
||||
except subprocess.CalledProcessError as e2:
|
||||
self.print_error(f"Failed to create virtual environment: {e2}")
|
||||
self.print_info("Error details:")
|
||||
if hasattr(e2, 'stderr') and e2.stderr:
|
||||
print(f" {e2.stderr}")
|
||||
self.print_info("Try manually: python3 -m venv venv")
|
||||
|
||||
if self.ask_yes_no("Continue without virtual environment? (not recommended)", False):
|
||||
self.python_executable = sys.executable
|
||||
self.print_warning("Proceeding without virtual environment")
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
def get_venv_python(self) -> str:
|
||||
"""Get path to Python executable in virtual environment"""
|
||||
@@ -245,10 +279,29 @@ class FishbowlSetup:
|
||||
"""Collect database configuration"""
|
||||
print("\n🗄️ Database Configuration")
|
||||
|
||||
db_choices = ["SQLite (simple, file-based)", "PostgreSQL (recommended for production)"]
|
||||
if self.docker_available:
|
||||
db_choices = [
|
||||
"SQLite (simple, file-based)",
|
||||
"PostgreSQL with Docker (recommended)",
|
||||
"PostgreSQL (manual setup)"
|
||||
]
|
||||
else:
|
||||
db_choices = ["SQLite (simple, file-based)", "PostgreSQL (manual setup)"]
|
||||
|
||||
db_choice = self.ask_choice("Choose database type:", db_choices, 0)
|
||||
|
||||
if "PostgreSQL" in db_choice:
|
||||
if "PostgreSQL with Docker" in db_choice:
|
||||
self.config["database"] = {
|
||||
"type": "postgresql",
|
||||
"host": "localhost",
|
||||
"port": 5432,
|
||||
"name": "discord_fishbowl",
|
||||
"username": "postgres",
|
||||
"password": self.ask_question("Database password", "fishbowl_password"),
|
||||
"use_docker": True
|
||||
}
|
||||
self.use_docker_services = True
|
||||
elif "PostgreSQL" in db_choice:
|
||||
self.config["database"] = {
|
||||
"type": "postgresql",
|
||||
"host": self.ask_question("PostgreSQL host", "localhost"),
|
||||
@@ -256,11 +309,13 @@ class FishbowlSetup:
|
||||
"name": self.ask_question("Database name", "discord_fishbowl"),
|
||||
"username": self.ask_question("Database username", "postgres"),
|
||||
"password": self.ask_question("Database password", secret=True),
|
||||
"use_docker": False
|
||||
}
|
||||
else:
|
||||
self.config["database"] = {
|
||||
"type": "sqlite",
|
||||
"path": "data/fishbowl.db"
|
||||
"path": "data/fishbowl.db",
|
||||
"use_docker": False
|
||||
}
|
||||
self.print_info("SQLite database will be created automatically")
|
||||
|
||||
@@ -270,36 +325,85 @@ class FishbowlSetup:
|
||||
self.print_info("Redis is used for caching and pub/sub messaging")
|
||||
|
||||
if self.ask_yes_no("Use Redis? (recommended)", True):
|
||||
self.config["redis"] = {
|
||||
"enabled": True,
|
||||
"host": self.ask_question("Redis host", "localhost"),
|
||||
"port": int(self.ask_question("Redis port", "6379")),
|
||||
"password": self.ask_question("Redis password (leave empty if none)", "", required=False),
|
||||
"db": int(self.ask_question("Redis database number", "0")),
|
||||
}
|
||||
if self.docker_available and hasattr(self, 'use_docker_services') and self.use_docker_services:
|
||||
if self.ask_yes_no("Use Redis with Docker?", True):
|
||||
self.config["redis"] = {
|
||||
"enabled": True,
|
||||
"host": "localhost",
|
||||
"port": 6379,
|
||||
"password": self.ask_question("Redis password", "redis_password"),
|
||||
"db": 0,
|
||||
"use_docker": True
|
||||
}
|
||||
else:
|
||||
self.config["redis"] = {
|
||||
"enabled": True,
|
||||
"host": self.ask_question("Redis host", "localhost"),
|
||||
"port": int(self.ask_question("Redis port", "6379")),
|
||||
"password": self.ask_question("Redis password (leave empty if none)", "", required=False),
|
||||
"db": int(self.ask_question("Redis database number", "0")),
|
||||
"use_docker": False
|
||||
}
|
||||
else:
|
||||
self.config["redis"] = {
|
||||
"enabled": True,
|
||||
"host": self.ask_question("Redis host", "localhost"),
|
||||
"port": int(self.ask_question("Redis port", "6379")),
|
||||
"password": self.ask_question("Redis password (leave empty if none)", "", required=False),
|
||||
"db": int(self.ask_question("Redis database number", "0")),
|
||||
"use_docker": False
|
||||
}
|
||||
else:
|
||||
self.config["redis"] = {"enabled": False}
|
||||
self.config["redis"] = {"enabled": False, "use_docker": False}
|
||||
|
||||
def collect_vector_db_config(self):
|
||||
"""Collect vector database configuration"""
|
||||
print("\n🔍 Vector Database Configuration")
|
||||
self.print_info("Vector database stores character memories and enables semantic search")
|
||||
|
||||
vector_choices = ["Qdrant (recommended)", "In-memory (for testing)", "Skip vector database"]
|
||||
if self.docker_available:
|
||||
vector_choices = [
|
||||
"ChromaDB with Docker (simple)",
|
||||
"Qdrant with Docker (recommended)",
|
||||
"Qdrant (manual setup)",
|
||||
"In-memory (for testing)",
|
||||
"Skip vector database"
|
||||
]
|
||||
else:
|
||||
vector_choices = ["Qdrant (manual setup)", "In-memory (for testing)", "Skip vector database"]
|
||||
|
||||
vector_choice = self.ask_choice("Choose vector database:", vector_choices, 0)
|
||||
|
||||
if "Qdrant" in vector_choice:
|
||||
if "ChromaDB with Docker" in vector_choice:
|
||||
self.config["vector_db"] = {
|
||||
"type": "chromadb",
|
||||
"host": "localhost",
|
||||
"port": 8000,
|
||||
"use_docker": True
|
||||
}
|
||||
self.use_docker_services = True
|
||||
elif "Qdrant with Docker" in vector_choice:
|
||||
self.config["vector_db"] = {
|
||||
"type": "qdrant",
|
||||
"host": "localhost",
|
||||
"port": 6333,
|
||||
"collection_name": self.ask_question("Collection name", "fishbowl_memories"),
|
||||
"use_docker": True
|
||||
}
|
||||
self.use_docker_services = True
|
||||
elif "Qdrant" in vector_choice:
|
||||
self.config["vector_db"] = {
|
||||
"type": "qdrant",
|
||||
"host": self.ask_question("Qdrant host", "localhost"),
|
||||
"port": int(self.ask_question("Qdrant port", "6333")),
|
||||
"collection_name": self.ask_question("Collection name", "fishbowl_memories"),
|
||||
"use_docker": False
|
||||
}
|
||||
elif "In-memory" in vector_choice:
|
||||
self.config["vector_db"] = {"type": "memory"}
|
||||
self.config["vector_db"] = {"type": "memory", "use_docker": False}
|
||||
self.print_warning("In-memory vector database won't persist between restarts")
|
||||
else:
|
||||
self.config["vector_db"] = {"type": "none"}
|
||||
self.config["vector_db"] = {"type": "none", "use_docker": False}
|
||||
self.print_warning("Without vector database, character memories will be limited")
|
||||
|
||||
def collect_ai_config(self):
|
||||
@@ -620,6 +724,91 @@ python -m src.admin.app
|
||||
self.print_warning("Character initialization failed - you can run it manually later")
|
||||
self.print_info("Run: python -m scripts.init_characters")
|
||||
|
||||
def setup_docker_services(self):
|
||||
"""Set up Docker services if enabled"""
|
||||
if not self.use_docker_services:
|
||||
return
|
||||
|
||||
self.print_section("Setting Up Docker Services")
|
||||
|
||||
# Create .env file for Docker Compose
|
||||
docker_env_content = self.create_docker_env_content()
|
||||
docker_env_file = self.project_root / ".env.docker"
|
||||
with open(docker_env_file, "w") as f:
|
||||
f.write(docker_env_content)
|
||||
self.print_success("Docker environment file created")
|
||||
|
||||
# Start Docker services
|
||||
if self.ask_yes_no("Start Docker services now?", True):
|
||||
try:
|
||||
self.print_info("Starting PostgreSQL and Redis containers...")
|
||||
subprocess.run([
|
||||
"docker", "compose", "-f", "docker-compose.services.yml",
|
||||
"--env-file", ".env.docker", "up", "-d"
|
||||
], check=True, cwd=self.project_root)
|
||||
self.print_success("Docker services started successfully")
|
||||
|
||||
# Wait for services to be ready
|
||||
self.print_info("Waiting for services to be ready...")
|
||||
import time
|
||||
time.sleep(10)
|
||||
|
||||
# Check service health
|
||||
self.check_docker_services()
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
self.print_error(f"Failed to start Docker services: {e}")
|
||||
self.print_info("You can start them manually with: docker compose -f docker-compose.services.yml up -d")
|
||||
else:
|
||||
self.print_info("To start services later: docker compose -f docker-compose.services.yml --env-file .env.docker up -d")
|
||||
|
||||
def create_docker_env_content(self) -> str:
|
||||
"""Create Docker environment file content"""
|
||||
lines = [
|
||||
"# Docker Compose Environment Variables",
|
||||
"# Generated by Discord Fishbowl setup script",
|
||||
"",
|
||||
]
|
||||
|
||||
if self.config["database"].get("use_docker"):
|
||||
lines.extend([
|
||||
f"DB_PASSWORD={self.config['database']['password']}",
|
||||
"",
|
||||
])
|
||||
|
||||
if self.config["redis"].get("use_docker"):
|
||||
lines.extend([
|
||||
f"REDIS_PASSWORD={self.config['redis']['password']}",
|
||||
"",
|
||||
])
|
||||
|
||||
lines.extend([
|
||||
"# Optional PgAdmin credentials (if using --profile admin)",
|
||||
"PGADMIN_PASSWORD=admin123",
|
||||
"",
|
||||
])
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def check_docker_services(self):
|
||||
"""Check if Docker services are running"""
|
||||
try:
|
||||
result = subprocess.run([
|
||||
"docker", "compose", "-f", "docker-compose.services.yml", "ps", "--services", "--filter", "status=running"
|
||||
], check=True, capture_output=True, text=True, cwd=self.project_root)
|
||||
|
||||
running_services = result.stdout.strip().split('\n') if result.stdout.strip() else []
|
||||
|
||||
if "postgres" in running_services:
|
||||
self.print_success("PostgreSQL is running")
|
||||
if "redis" in running_services:
|
||||
self.print_success("Redis is running")
|
||||
if "chromadb" in running_services:
|
||||
self.print_success("ChromaDB is running")
|
||||
|
||||
except subprocess.CalledProcessError:
|
||||
self.print_warning("Could not check Docker service status")
|
||||
|
||||
def print_completion_summary(self):
|
||||
"""Print setup completion summary"""
|
||||
self.print_section("🎉 Setup Complete!")
|
||||
@@ -693,6 +882,9 @@ python -m src.admin.app
|
||||
self.collect_configuration()
|
||||
self.create_config_files()
|
||||
|
||||
# Docker services setup
|
||||
self.setup_docker_services()
|
||||
|
||||
# Database and scripts
|
||||
self.setup_database_schema()
|
||||
self.create_startup_scripts()
|
||||
|
||||
Reference in New Issue
Block a user