Files
punimtag/install.sh
T
ilia cb9e927ad9
CI / skip-ci-check (pull_request) Successful in 30s
CI / docker-ci (pull_request) Successful in 32s
CI / python-lint (pull_request) Successful in 32s
CI / secret-scan (pull_request) Successful in 39s
CI / viewer-unit (pull_request) Successful in 2m21s
CI / admin-unit (pull_request) Successful in 2m43s
CI / e2e (pull_request) Successful in 3m22s
Scrub docs/scripts of example passwords and host paths.
Stop shipping punimtag_password / admin defaults in install helpers and
docs; generate secrets at install time; require ADMIN_PASSWORD from env
(with test-only bootstrap in conftest).
2026-08-05 12:28:56 -04:00

518 lines
17 KiB
Bash
Executable File

#!/bin/bash
# Comprehensive installation script for PunimTag Web
# Installs all system dependencies, Python packages, and frontend dependencies
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Project root directory
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$PROJECT_ROOT"
echo -e "${BLUE}╔══════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ PunimTag Web - Comprehensive Installation Script ║${NC}"
echo -e "${BLUE}╚══════════════════════════════════════════════════════════╝${NC}"
echo ""
# Function to check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Generate a local-only secret (never commit the resulting .env)
gen_secret() {
if command_exists openssl; then
openssl rand -hex 16
else
head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n' | head -c 32
fi
}
# Function to check Python version
check_python_version() {
if command_exists python3; then
PYTHON_VERSION=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')
PYTHON_MAJOR=$(echo "$PYTHON_VERSION" | cut -d. -f1)
PYTHON_MINOR=$(echo "$PYTHON_VERSION" | cut -d. -f2)
if [ "$PYTHON_MAJOR" -lt 3 ] || ([ "$PYTHON_MAJOR" -eq 3 ] && [ "$PYTHON_MINOR" -lt 12 ]); then
echo -e "${RED}❌ Python 3.12 or higher is required. Found: Python $PYTHON_VERSION${NC}"
return 1
else
echo -e "${GREEN}✅ Python $PYTHON_VERSION found${NC}"
return 0
fi
else
echo -e "${RED}❌ Python 3 is not installed${NC}"
return 1
fi
}
# Function to check Node.js version
check_node_version() {
if command_exists node; then
NODE_VERSION=$(node -v | sed 's/v//')
NODE_MAJOR=$(echo "$NODE_VERSION" | cut -d. -f1)
if [ "$NODE_MAJOR" -lt 18 ]; then
echo -e "${RED}❌ Node.js 18 or higher is required. Found: Node.js $NODE_VERSION${NC}"
return 1
else
echo -e "${GREEN}✅ Node.js $NODE_VERSION found${NC}"
return 0
fi
else
echo -e "${RED}❌ Node.js is not installed${NC}"
return 1
fi
}
# Detect OS
detect_os() {
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
if [ -f /etc/os-release ]; then
. /etc/os-release
OS=$ID
else
OS="linux"
fi
elif [[ "$OSTYPE" == "darwin"* ]]; then
OS="macos"
else
OS="unknown"
fi
echo "$OS"
}
# Install system dependencies (Linux only)
install_system_dependencies() {
local os=$(detect_os)
if [[ "$os" != "ubuntu" && "$os" != "debian" ]]; then
echo -e "${YELLOW}⚠️ Automatic system dependency installation is only supported on Ubuntu/Debian${NC}"
echo -e "${YELLOW} Please install PostgreSQL and Redis manually for other systems${NC}"
return 0
fi
echo -e "${BLUE}📦 Installing system dependencies...${NC}"
# Update package list
sudo apt update
# Install PostgreSQL
if ! command_exists psql; then
echo -e "${BLUE} Installing PostgreSQL...${NC}"
sudo apt install -y postgresql postgresql-contrib
sudo systemctl start postgresql
sudo systemctl enable postgresql
echo -e "${GREEN} ✅ PostgreSQL installed${NC}"
else
echo -e "${GREEN} ✅ PostgreSQL already installed${NC}"
fi
# Install Redis
if ! command_exists redis-cli; then
echo -e "${BLUE} Installing Redis...${NC}"
sudo apt install -y redis-server
sudo systemctl start redis-server
sudo systemctl enable redis-server
echo -e "${GREEN} ✅ Redis installed${NC}"
else
echo -e "${GREEN} ✅ Redis already installed${NC}"
fi
# Install Python tkinter (required for native folder picker)
if ! python3 -c "import tkinter" 2>/dev/null; then
echo -e "${BLUE} Installing Python tkinter...${NC}"
sudo apt install -y python3-tk
echo -e "${GREEN} ✅ Python tkinter installed${NC}"
else
echo -e "${GREEN} ✅ Python tkinter already installed${NC}"
fi
# Verify Redis is running
if redis-cli ping >/dev/null 2>&1; then
echo -e "${GREEN} ✅ Redis is running${NC}"
else
echo -e "${YELLOW} ⚠️ Redis is not running. Starting Redis...${NC}"
sudo systemctl start redis-server || redis-server --daemonize yes
fi
}
# Setup PostgreSQL databases
setup_databases() {
echo -e "${BLUE}🗄️ Setting up PostgreSQL databases...${NC}"
if ! command_exists psql; then
echo -e "${YELLOW}⚠️ PostgreSQL is not installed. Skipping database setup.${NC}"
echo -e "${YELLOW} Please install PostgreSQL and run: ./scripts/setup_postgresql.sh${NC}"
return 0
fi
# Start PostgreSQL if not running
if ! sudo systemctl is-active --quiet postgresql; then
echo -e "${BLUE} Starting PostgreSQL service...${NC}"
sudo systemctl start postgresql
fi
# Create user and databases (password from env or freshly generated)
echo -e "${BLUE} Creating database user and databases...${NC}"
DB_PASSWORD="${PUNIMTAG_DB_PASSWORD:-$(gen_secret)}"
export PUNIMTAG_DB_PASSWORD="$DB_PASSWORD"
sudo -u postgres psql << EOF || true
-- Create user if it doesn't exist
DO \$\$
BEGIN
IF NOT EXISTS (SELECT FROM pg_user WHERE usename = 'punimtag') THEN
CREATE USER punimtag WITH PASSWORD '${DB_PASSWORD}';
END IF;
END
\$\$;
-- Create main database if it doesn't exist
SELECT 'CREATE DATABASE punimtag OWNER punimtag'
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'punimtag')\gexec
-- Create auth database if it doesn't exist
SELECT 'CREATE DATABASE punimtag_auth OWNER punimtag'
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'punimtag_auth')\gexec
-- Grant privileges
GRANT ALL PRIVILEGES ON DATABASE punimtag TO punimtag;
GRANT ALL PRIVILEGES ON DATABASE punimtag_auth TO punimtag;
\q
EOF
echo -e "${GREEN} ✅ Databases created${NC}"
echo -e "${YELLOW} ⚠️ DB password stored only in .env (generated for this install)${NC}"
}
# Create Python virtual environment
setup_python_venv() {
echo -e "${BLUE}🐍 Setting up Python virtual environment...${NC}"
if [ ! -d "venv" ]; then
echo -e "${BLUE} Creating virtual environment...${NC}"
python3 -m venv venv
echo -e "${GREEN} ✅ Virtual environment created${NC}"
else
echo -e "${GREEN} ✅ Virtual environment already exists${NC}"
fi
# Activate virtual environment
source venv/bin/activate
# Upgrade pip
echo -e "${BLUE} Upgrading pip...${NC}"
pip install --upgrade pip --quiet
echo -e "${GREEN} ✅ Python environment ready${NC}"
}
# Install Python dependencies
install_python_dependencies() {
echo -e "${BLUE}📦 Installing Python dependencies...${NC}"
if [ ! -f "requirements.txt" ]; then
echo -e "${RED}❌ requirements.txt not found${NC}"
return 1
fi
source venv/bin/activate
pip install -r requirements.txt
echo -e "${GREEN} ✅ Python dependencies installed${NC}"
}
# Download DeepFace model weights (optional, but recommended)
download_deepface_models() {
echo -e "${BLUE}🤖 Downloading DeepFace model weights (optional)...${NC}"
source venv/bin/activate
# Check if deepface is installed
if ! python3 -c "import deepface" 2>/dev/null; then
echo -e "${YELLOW} ⚠️ DeepFace not installed, skipping model download${NC}"
return 0
fi
# Create weights directory
mkdir -p ~/.deepface/weights
# Check if ArcFace weights already exist
if [ -f ~/.deepface/weights/arcface_weights.h5 ]; then
echo -e "${GREEN} ✅ ArcFace model weights already exist${NC}"
return 0
fi
echo -e "${BLUE} Downloading ArcFace model weights (~131MB, this may take a few minutes)...${NC}"
# Try to download using wget (more reliable than Python download in worker)
if command_exists wget; then
if wget -q --show-progress \
https://github.com/serengil/deepface_models/releases/download/v1.0/arcface_weights.h5 \
-O ~/.deepface/weights/arcface_weights.h5; then
echo -e "${GREEN} ✅ ArcFace model weights downloaded successfully${NC}"
return 0
fi
fi
# Fallback: try with Python (may fail in some environments)
echo -e "${YELLOW} ⚠️ wget failed, trying Python download...${NC}"
if python3 << 'PYTHON'
import os
import sys
from pathlib import Path
try:
from deepface import DeepFace
import numpy as np
from PIL import Image
# Create a dummy image to trigger model download
dummy_img = Image.new('RGB', (100, 100), color='black')
dummy_array = np.array(dummy_img)
# This will trigger model download
try:
DeepFace.represent(
img_path=dummy_array,
model_name="ArcFace",
detector_backend="retinaface",
enforce_detection=False,
align=True,
)
print("✅ Model weights downloaded successfully")
sys.exit(0)
except Exception as e:
if "arcface_weights" in str(e).lower():
print(f"⚠️ Download failed: {e}")
print(" You can download manually later or the worker will try again")
sys.exit(1)
# Other errors are OK (like no faces found)
print("✅ Model weights are available")
sys.exit(0)
except ImportError:
print("⚠️ DeepFace not available")
sys.exit(0)
except Exception as e:
print(f"⚠️ Error: {e}")
sys.exit(1)
PYTHON
then
echo -e "${GREEN} ✅ DeepFace models ready${NC}"
else
echo -e "${YELLOW} ⚠️ Automatic download failed. Models will be downloaded on first use.${NC}"
echo -e "${YELLOW} ⚠️ To download manually, run:${NC}"
echo -e "${BLUE} mkdir -p ~/.deepface/weights${NC}"
echo -e "${BLUE} wget https://github.com/serengil/deepface_models/releases/download/v1.0/arcface_weights.h5 -O ~/.deepface/weights/arcface_weights.h5${NC}"
fi
}
# Install frontend dependencies
install_frontend_dependencies() {
echo -e "${BLUE}📦 Installing frontend dependencies...${NC}"
# Install admin frontend
if [ -d "admin-frontend" ]; then
echo -e "${BLUE} Installing admin frontend dependencies...${NC}"
cd admin-frontend
if [ -f "package.json" ]; then
npm install
echo -e "${GREEN} ✅ Admin frontend dependencies installed${NC}"
fi
cd ..
fi
# Install viewer frontend
if [ -d "viewer-frontend" ]; then
echo -e "${BLUE} Installing viewer frontend dependencies...${NC}"
cd viewer-frontend
if [ -f "package.json" ]; then
npm install
echo -e "${GREEN} ✅ Viewer frontend dependencies installed${NC}"
fi
cd ..
fi
}
# Create .env file if it doesn't exist
create_env_file() {
echo -e "${BLUE}⚙️ Setting up environment configuration...${NC}"
if [ ! -f ".env" ]; then
echo -e "${BLUE} Creating .env file...${NC}"
DB_PASSWORD="${PUNIMTAG_DB_PASSWORD:-$(gen_secret)}"
ADMIN_PASSWORD="${PUNIMTAG_ADMIN_PASSWORD:-$(gen_secret)}"
SECRET_KEY="$(gen_secret)$(gen_secret)"
cat > .env << EOF
# Database (PostgreSQL by default) — password generated at install; do not commit .env
DATABASE_URL=postgresql+psycopg2://punimtag:${DB_PASSWORD}@localhost:5432/punimtag
# Auth Database (for frontend website user accounts - separate from main database)
DATABASE_URL_AUTH=postgresql+psycopg2://punimtag:${DB_PASSWORD}@localhost:5432/punimtag_auth
# JWT Secrets (change in production!)
SECRET_KEY=${SECRET_KEY}
# Admin credentials (generated; change if this host is exposed)
ADMIN_USERNAME=admin
ADMIN_PASSWORD=${ADMIN_PASSWORD}
# Photo storage directory (default: data/uploads)
PHOTO_STORAGE_DIR=data/uploads
EOF
echo -e "${GREEN} ✅ .env file created${NC}"
echo -e "${YELLOW} ⚠️ Credentials are in .env only — never commit that file${NC}"
else
echo -e "${GREEN} ✅ .env file already exists${NC}"
fi
# Create admin frontend .env file if it doesn't exist
if [ -d "admin-frontend" ] && [ ! -f "admin-frontend/.env" ]; then
echo -e "${BLUE} Creating admin-frontend/.env file...${NC}"
cat > admin-frontend/.env << 'EOF'
# Backend API URL (must be accessible from browsers)
VITE_API_URL=http://127.0.0.1:8000
EOF
echo -e "${GREEN} ✅ admin-frontend/.env file created${NC}"
elif [ -d "admin-frontend" ]; then
echo -e "${GREEN} ✅ admin-frontend/.env file already exists${NC}"
fi
# Create viewer frontend .env file if it doesn't exist
if [ -d "viewer-frontend" ] && [ ! -f "viewer-frontend/.env.local" ]; then
echo -e "${BLUE} Creating viewer-frontend/.env.local file...${NC}"
DB_PASSWORD="${PUNIMTAG_DB_PASSWORD:-$(gen_secret)}"
NEXTAUTH_SECRET="$(gen_secret)$(gen_secret)"
cat > viewer-frontend/.env.local << EOF
# Database connection (read-only recommended) — do not commit
DATABASE_URL=postgresql://punimtag:${DB_PASSWORD}@localhost:5432/punimtag
# Auth database connection
DATABASE_URL_AUTH=postgresql://punimtag:${DB_PASSWORD}@localhost:5432/punimtag_auth
# NextAuth configuration
NEXTAUTH_URL=http://localhost:3001
NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
EOF
echo -e "${GREEN} ✅ viewer-frontend/.env.local file created${NC}"
elif [ -d "viewer-frontend" ]; then
echo -e "${GREEN} ✅ viewer-frontend/.env.local file already exists${NC}"
fi
}
# Create data directories
create_data_directories() {
echo -e "${BLUE}📁 Creating data directories...${NC}"
mkdir -p data/uploads
mkdir -p data/thumbnails
echo -e "${GREEN} ✅ Data directories created${NC}"
}
# Main installation function
main() {
echo -e "${BLUE}🔍 Checking prerequisites...${NC}"
echo ""
# Check Python
if ! check_python_version; then
echo -e "${RED}❌ Please install Python 3.12 or higher${NC}"
exit 1
fi
# Check Node.js
if ! check_node_version; then
echo -e "${RED}❌ Please install Node.js 18 or higher${NC}"
exit 1
fi
# Check npm
if ! command_exists npm; then
echo -e "${RED}❌ npm is not installed${NC}"
exit 1
else
echo -e "${GREEN}✅ npm found${NC}"
fi
echo ""
echo -e "${BLUE}🚀 Starting installation...${NC}"
echo ""
# Install system dependencies (Linux only)
install_system_dependencies
echo ""
# Setup databases
setup_databases
echo ""
# Setup Python environment
setup_python_venv
echo ""
# Install Python dependencies
install_python_dependencies
echo ""
# Download DeepFace model weights (optional)
download_deepface_models
echo ""
# Install frontend dependencies
install_frontend_dependencies
echo ""
# Create .env files
create_env_file
echo ""
# Create data directories
create_data_directories
echo ""
# Success message
echo -e "${GREEN}╔══════════════════════════════════════════════════════════╗${NC}"
echo -e "${GREEN}║ ✅ Installation Complete! ║${NC}"
echo -e "${GREEN}╚══════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e "${BLUE}📋 Next Steps:${NC}"
echo ""
echo -e "1. ${YELLOW}Review and update .env file${NC} (if needed):"
echo -e " ${BLUE} nano .env${NC}"
echo ""
echo -e "2. ${YELLOW}Activate the virtual environment:${NC}"
echo -e " ${BLUE} source venv/bin/activate${NC}"
echo ""
echo -e "3. ${YELLOW}Start the backend API:${NC}"
echo -e " ${BLUE} export PYTHONPATH=$PROJECT_ROOT${NC}"
echo -e " ${BLUE} uvicorn backend.app:app --host 127.0.0.1 --port 8000${NC}"
echo ""
echo -e "4. ${YELLOW}In another terminal, start the admin frontend:${NC}"
echo -e " ${BLUE} cd admin-frontend${NC}"
echo -e " ${BLUE} npm run dev${NC}"
echo ""
echo -e "5. ${YELLOW}In another terminal, start the viewer frontend (optional):${NC}"
echo -e " ${BLUE} cd viewer-frontend${NC}"
echo -e " ${BLUE} npm run dev${NC}"
echo ""
echo -e "6. ${YELLOW}Access the applications:${NC}"
echo -e " ${BLUE} Admin: http://localhost:3000${NC}"
echo -e " ${BLUE} Viewer: http://localhost:3001${NC}"
echo ""
echo -e "${GREEN}For more information, see README.md${NC}"
echo ""
}
# Run main function
main