FASE 4: - Redis cache de stock con fallback graceful - Multi-moneda (MXN/USD) con contabilidad en MXN - Proveedores y ordenes de compra completo - Meilisearch 1.5M+ partes indexadas - Metabase KPIs con dashboard auto-generado FASE 5: - CRM mejorado: activities, tags, loyalty program, analytics - Imagenes de partes: upload, resize, thumbnails WebP - Ordenes de servicio Kanban: received->diagnosis->repair->ready->delivered - Garantias/RMA, alertas de reorden, multi-sucursal - Stubs BNPL (APLAZO) y ERP Sync (Aspel/Contpaqi) FASE 6: - Notificaciones automaticas: push/WhatsApp/email/in-app - Reportes de ahorro vs retail_price - Logistica + tracking: DHL, FedEx, Estafeta, 99min, Uber - API Publica: API keys, rate limiting, catalog search Migraciones: v1.9-v3.0 Tests: 93/93 pasando Backup: nexus_backup_20260427_045859.tar.gz
680 lines
21 KiB
Bash
Executable File
680 lines
21 KiB
Bash
Executable File
#!/bin/bash
|
|
# ============================================================
|
|
# Nexus Autoparts POS — Automated Installer v2.0
|
|
# Works on: Debian 12+, Ubuntu 22.04+, Raspberry Pi OS (64-bit)
|
|
# ============================================================
|
|
set -euo pipefail
|
|
|
|
# ----- Colors & helpers -----
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m'
|
|
|
|
INSTALL_DIR="/opt/nexus-pos"
|
|
LOG_FILE="/var/log/nexus-pos-install.log"
|
|
|
|
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
|
|
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
err() { echo -e "${RED}[ERROR]${NC} $*"; }
|
|
fatal() { err "$*"; exit 1; }
|
|
|
|
banner() {
|
|
echo ""
|
|
echo -e "${BOLD}${CYAN}"
|
|
echo " ========================================"
|
|
echo " Nexus Autoparts POS — Installer v2.0"
|
|
echo " ========================================"
|
|
echo -e "${NC}"
|
|
}
|
|
|
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"; }
|
|
|
|
cleanup_on_error() {
|
|
err "Installation failed. Check $LOG_FILE for details."
|
|
exit 1
|
|
}
|
|
trap cleanup_on_error ERR
|
|
|
|
# Generate a secure random password
|
|
generate_secret() {
|
|
openssl rand -hex 32 2>/dev/null || python3 -c "import secrets; print(secrets.token_hex(32))"
|
|
}
|
|
|
|
# ============================================================
|
|
# 1. CHECK PREREQUISITES
|
|
# ============================================================
|
|
check_prerequisites() {
|
|
info "Checking prerequisites..."
|
|
|
|
if [[ "$(uname -s)" != "Linux" ]]; then
|
|
fatal "This installer only supports Linux (Debian/Ubuntu/Raspberry Pi OS)."
|
|
fi
|
|
|
|
if [[ $EUID -ne 0 ]]; then
|
|
fatal "This script must be run as root. Use: sudo bash install.sh"
|
|
fi
|
|
|
|
if [[ -f /etc/os-release ]]; then
|
|
. /etc/os-release
|
|
info "Detected OS: ${PRETTY_NAME:-$ID}"
|
|
log "OS: ${PRETTY_NAME:-$ID}"
|
|
fi
|
|
|
|
IS_RPI=false
|
|
if grep -qi "raspberry" /proc/cpuinfo 2>/dev/null || grep -qi "raspberry" /sys/firmware/devicetree/base/model 2>/dev/null; then
|
|
IS_RPI=true
|
|
info "Raspberry Pi detected."
|
|
fi
|
|
|
|
ARCH=$(uname -m)
|
|
info "Architecture: $ARCH"
|
|
|
|
if ! ping -c 1 -W 3 8.8.8.8 &>/dev/null; then
|
|
warn "No internet connection detected. Some features may not work."
|
|
fi
|
|
|
|
ok "Prerequisites check passed."
|
|
}
|
|
|
|
# ============================================================
|
|
# 2. INSTALL SYSTEM PACKAGES
|
|
# ============================================================
|
|
install_packages() {
|
|
info "Updating package lists..."
|
|
apt-get update -qq >> "$LOG_FILE" 2>&1
|
|
|
|
PACKAGES=(
|
|
python3 python3-pip python3-venv
|
|
postgresql postgresql-client
|
|
redis-server
|
|
git nginx curl
|
|
libpq-dev gcc python3-dev
|
|
)
|
|
|
|
if [[ "$IS_RPI" == true ]]; then
|
|
PACKAGES+=(libxml2-dev libxslt1-dev zlib1g-dev)
|
|
fi
|
|
|
|
info "Installing system packages..."
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "${PACKAGES[@]}" >> "$LOG_FILE" 2>&1
|
|
|
|
# Install Node.js for WhatsApp bridge (LTS)
|
|
if ! command -v node &>/dev/null; then
|
|
info "Installing Node.js LTS..."
|
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - >> "$LOG_FILE" 2>&1
|
|
apt-get install -y -qq nodejs >> "$LOG_FILE" 2>&1
|
|
fi
|
|
|
|
ok "System packages installed."
|
|
}
|
|
|
|
# ============================================================
|
|
# 3. CONFIGURE POSTGRESQL
|
|
# ============================================================
|
|
configure_postgresql() {
|
|
info "Configuring PostgreSQL..."
|
|
|
|
systemctl enable postgresql >> "$LOG_FILE" 2>&1
|
|
systemctl start postgresql >> "$LOG_FILE" 2>&1
|
|
|
|
info "Configuring Redis..."
|
|
systemctl enable redis-server >> "$LOG_FILE" 2>&1
|
|
systemctl start redis-server >> "$LOG_FILE" 2>&1
|
|
|
|
# Generate random DB password
|
|
DB_PASS=$(generate_secret)
|
|
DB_USER="nexus"
|
|
DB_NAME="nexus_autoparts"
|
|
|
|
# Save credentials for later
|
|
echo "DB_USER=$DB_USER" > /tmp/nexus_install_vars
|
|
echo "DB_PASS=$DB_PASS" >> /tmp/nexus_install_vars
|
|
echo "DB_NAME=$DB_NAME" >> /tmp/nexus_install_vars
|
|
|
|
# Create user
|
|
if sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${DB_USER}'" | grep -q 1; then
|
|
info "PostgreSQL user '${DB_USER}' exists. Updating password..."
|
|
sudo -u postgres psql -c "ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASS}' CREATEDB;" >> "$LOG_FILE" 2>&1
|
|
else
|
|
sudo -u postgres psql -c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASS}' CREATEDB;" >> "$LOG_FILE" 2>&1
|
|
ok "PostgreSQL user '${DB_USER}' created."
|
|
fi
|
|
|
|
# Create master database
|
|
if sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname='${DB_NAME}'" | grep -q 1; then
|
|
info "Database '${DB_NAME}' already exists."
|
|
else
|
|
sudo -u postgres psql -c "CREATE DATABASE ${DB_NAME} OWNER ${DB_USER};" >> "$LOG_FILE" 2>&1
|
|
ok "Database '${DB_NAME}' created."
|
|
fi
|
|
|
|
# Ensure md5 auth for local connections
|
|
PG_HBA=$(sudo -u postgres psql -tAc "SHOW hba_file" 2>/dev/null | head -1 | xargs)
|
|
if [[ -n "$PG_HBA" ]] && ! grep -q "local.*all.*${DB_USER}.*scram-sha-256" "$PG_HBA" 2>/dev/null; then
|
|
# Add scram-sha-256 line before the first peer/trust line
|
|
sed -i "/^local.*all.*all.*peer/i local all ${DB_USER} scram-sha-256" "$PG_HBA" 2>/dev/null || true
|
|
systemctl reload postgresql >> "$LOG_FILE" 2>&1
|
|
fi
|
|
|
|
ok "PostgreSQL configured."
|
|
}
|
|
|
|
# ============================================================
|
|
# 4. SETUP APPLICATION
|
|
# ============================================================
|
|
setup_app() {
|
|
info "Setting up application in ${INSTALL_DIR}..."
|
|
|
|
if [[ -d "${INSTALL_DIR}" ]]; then
|
|
warn "${INSTALL_DIR} already exists. Updating in place..."
|
|
else
|
|
mkdir -p "${INSTALL_DIR}"
|
|
fi
|
|
|
|
# Copy from local source (the repo where this script lives)
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
if [[ -f "${SCRIPT_DIR}/pos/app.py" ]]; then
|
|
info "Copying from local source: ${SCRIPT_DIR}"
|
|
rsync -a --delete --exclude='venv' --exclude='__pycache__' --exclude='.git' \
|
|
"${SCRIPT_DIR}/" "${INSTALL_DIR}/" >> "$LOG_FILE" 2>&1
|
|
else
|
|
fatal "Could not find local source. Run this script from the project root."
|
|
fi
|
|
|
|
# Start Meilisearch and Metabase (optional but recommended)
|
|
if command -v docker &>/dev/null; then
|
|
info "Starting Meilisearch..."
|
|
cd "${INSTALL_DIR}/docker" && docker compose -f docker-compose.meilisearch.yml up -d >> "$LOG_FILE" 2>&1
|
|
ok "Meilisearch container started."
|
|
info "Starting Metabase..."
|
|
cd "${INSTALL_DIR}/docker" && docker compose -f docker-compose.metabase.yml up -d >> "$LOG_FILE" 2>&1
|
|
ok "Metabase container started."
|
|
else
|
|
warn "Docker not found. Meilisearch and Metabase will not be available."
|
|
fi
|
|
|
|
# Create virtual environment
|
|
python3 -m venv "${INSTALL_DIR}/venv" >> "$LOG_FILE" 2>&1
|
|
"${INSTALL_DIR}/venv/bin/pip" install --upgrade pip >> "$LOG_FILE" 2>&1
|
|
"${INSTALL_DIR}/venv/bin/pip" install -r "${INSTALL_DIR}/pos/requirements.txt" >> "$LOG_FILE" 2>&1
|
|
"${INSTALL_DIR}/venv/bin/pip" install gunicorn >> "$LOG_FILE" 2>&1
|
|
|
|
ok "Application files and Python dependencies ready."
|
|
}
|
|
|
|
# ============================================================
|
|
# 5. INTERACTIVE SETUP
|
|
# ============================================================
|
|
interactive_setup() {
|
|
echo ""
|
|
echo -e "${BOLD}${CYAN}--- Business Setup ---${NC}"
|
|
echo ""
|
|
|
|
echo -en "${BOLD} Business name${NC} (e.g., Refaccionaria Lopez): "
|
|
read -r BUSINESS_NAME
|
|
[[ -z "$BUSINESS_NAME" ]] && BUSINESS_NAME="Mi Refaccionaria"
|
|
|
|
echo -en "${BOLD} RFC${NC} (optional): "
|
|
read -r BUSINESS_RFC
|
|
|
|
echo -en "${BOLD} Owner name${NC}: "
|
|
read -r OWNER_NAME
|
|
[[ -z "$OWNER_NAME" ]] && OWNER_NAME="Administrador"
|
|
|
|
while true; do
|
|
echo -en "${BOLD} Owner PIN${NC} (4 digits): "
|
|
read -rs OWNER_PIN
|
|
echo ""
|
|
[[ "$OWNER_PIN" =~ ^[0-9]{4}$ ]] && break
|
|
warn "PIN must be exactly 4 digits."
|
|
done
|
|
|
|
DEFAULT_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
|
|
echo -en "${BOLD} Domain or IP${NC} [${DEFAULT_IP:-localhost}]: "
|
|
read -r ACCESS_HOST
|
|
[[ -z "$ACCESS_HOST" ]] && ACCESS_HOST="${DEFAULT_IP:-localhost}"
|
|
|
|
echo ""
|
|
echo -e "${BOLD} Summary:${NC}"
|
|
echo " Business: ${BUSINESS_NAME}"
|
|
echo " RFC: ${BUSINESS_RFC:-N/A}"
|
|
echo " Owner: ${OWNER_NAME}"
|
|
echo " PIN: ****"
|
|
echo " Access: http://${ACCESS_HOST}"
|
|
echo ""
|
|
echo -en "${BOLD} Proceed? [Y/n]: ${NC}"
|
|
read -r confirm
|
|
[[ "${confirm,,}" == "n" ]] && fatal "Installation cancelled."
|
|
}
|
|
|
|
# ============================================================
|
|
# 6. LOAD MASTER SCHEMA & SEED DATA
|
|
# ============================================================
|
|
load_master_schema() {
|
|
info "Loading master database schema (vehicles + catalog)..."
|
|
|
|
source /tmp/nexus_install_vars
|
|
|
|
# Load PostgreSQL schema (no TecDoc)
|
|
sudo -u postgres psql "${DB_NAME}" -f "${INSTALL_DIR}/sql/schema_master_postgres.sql" >> "$LOG_FILE" 2>&1
|
|
|
|
# Load initial catalog seed (categories, brands, models, years)
|
|
sudo -u postgres psql "${DB_NAME}" -f "${INSTALL_DIR}/pos/seed/initial_catalog.sql" >> "$LOG_FILE" 2>&1
|
|
|
|
ok "Master schema and seed data loaded."
|
|
}
|
|
|
|
# ============================================================
|
|
# 7. PROVISION TENANT
|
|
# ============================================================
|
|
provision_tenant() {
|
|
info "Provisioning tenant database..."
|
|
|
|
source /tmp/nexus_install_vars
|
|
|
|
cd "${INSTALL_DIR}/pos"
|
|
|
|
export MASTER_DB_URL="postgresql://${DB_USER}:${DB_PASS}@localhost/${DB_NAME}"
|
|
export TENANT_DB_URL_TEMPLATE="postgresql://${DB_USER}:${DB_PASS}@localhost/{db_name}"
|
|
export POS_JWT_SECRET="$(generate_secret)"
|
|
export DATABASE_URL="${MASTER_DB_URL}"
|
|
export JWT_SECRET="${POS_JWT_SECRET}"
|
|
|
|
# Build provision script
|
|
cat > /tmp/_nexus_provision.py << 'PYEOF'
|
|
import sys, os
|
|
sys.path.insert(0, os.environ['INSTALL_DIR'] + '/pos')
|
|
os.chdir(os.environ['INSTALL_DIR'] + '/pos')
|
|
|
|
from services.tenant_manager import provision_tenant
|
|
|
|
result = provision_tenant(
|
|
name=os.environ['NX_BUSINESS'],
|
|
rfc=os.environ.get('NX_RFC') or None,
|
|
owner_name=os.environ['NX_OWNER'],
|
|
owner_pin=os.environ['NX_PIN']
|
|
)
|
|
print(f"Tenant created: id={result['tenant_id']}, db={result['db_name']}, subdomain={result['subdomain']}")
|
|
PYEOF
|
|
|
|
INSTALL_DIR="${INSTALL_DIR}" \
|
|
NX_BUSINESS="$BUSINESS_NAME" \
|
|
NX_RFC="$BUSINESS_RFC" \
|
|
NX_OWNER="$OWNER_NAME" \
|
|
NX_PIN="$OWNER_PIN" \
|
|
"${INSTALL_DIR}/venv/bin/python3" /tmp/_nexus_provision.py >> "$LOG_FILE" 2>&1
|
|
|
|
rm -f /tmp/_nexus_provision.py
|
|
|
|
ok "Tenant '${BUSINESS_NAME}' provisioned."
|
|
}
|
|
|
|
# ============================================================
|
|
# 8. APPLY MIGRATIONS TO ALL TENANTS
|
|
# ============================================================
|
|
apply_migrations() {
|
|
info "Applying database migrations..."
|
|
|
|
source /tmp/nexus_install_vars
|
|
export MASTER_DB_URL="postgresql://${DB_USER}:${DB_PASS}@localhost/${DB_NAME}"
|
|
export TENANT_DB_URL_TEMPLATE="postgresql://${DB_USER}:${DB_PASS}@localhost/{db_name}"
|
|
|
|
cd "${INSTALL_DIR}/pos"
|
|
|
|
# Run master migrations
|
|
"${INSTALL_DIR}/venv/bin/python3" -c "
|
|
import sys
|
|
sys.path.insert(0, '${INSTALL_DIR}/pos')
|
|
from migrations.runner_master import run_master_migrations
|
|
run_master_migrations()
|
|
" >> "$LOG_FILE" 2>&1
|
|
|
|
# Run tenant migrations
|
|
"${INSTALL_DIR}/venv/bin/python3" -c "
|
|
import sys
|
|
sys.path.insert(0, '${INSTALL_DIR}/pos')
|
|
from migrations.runner import run_migrations
|
|
run_migrations()
|
|
" >> "$LOG_FILE" 2>&1
|
|
|
|
ok "All migrations applied."
|
|
}
|
|
|
|
# ============================================================
|
|
# 9. GENERATE .env FILE
|
|
# ============================================================
|
|
generate_env() {
|
|
info "Generating environment configuration..."
|
|
|
|
source /tmp/nexus_install_vars
|
|
|
|
POS_SECRET=$(generate_secret)
|
|
WEB_SECRET=$(generate_secret)
|
|
WPP_SECRET=$(generate_secret)
|
|
|
|
cat > "${INSTALL_DIR}/.env" << ENVEOF
|
|
# Nexus Autoparts — Generated on $(date)
|
|
# DO NOT COMMIT THIS FILE TO GIT
|
|
|
|
DATABASE_URL=postgresql://${DB_USER}:${DB_PASS}@localhost/${DB_NAME}
|
|
MASTER_DB_URL=postgresql://${DB_USER}:${DB_PASS}@localhost/${DB_NAME}
|
|
TENANT_DB_URL_TEMPLATE=postgresql://${DB_USER}:${DB_PASS}@localhost/{db_name}
|
|
|
|
JWT_SECRET=${WEB_SECRET}
|
|
POS_JWT_SECRET=${POS_SECRET}
|
|
|
|
WHATSAPP_BRIDGE_KEY=${WPP_SECRET}
|
|
|
|
REDIS_URL=redis://localhost:6379/0
|
|
REDIS_ENABLED=true
|
|
REDIS_STOCK_TTL=300
|
|
|
|
MEILI_URL=http://localhost:7700
|
|
MEILI_API_KEY=$(generate_secret)
|
|
|
|
METABASE_URL=http://localhost:3000
|
|
METABASE_ADMIN_EMAIL=admin@${SUBDOMAIN}.local
|
|
METABASE_ADMIN_PASS=$(generate_secret)
|
|
METABASE_DB_PASS=$(generate_secret)
|
|
|
|
DEFAULT_CURRENCY=MXN
|
|
EXCHANGE_RATE_USD_MXN=17.5
|
|
ENVEOF
|
|
|
|
chmod 600 "${INSTALL_DIR}/.env"
|
|
ok ".env file created with secure secrets."
|
|
}
|
|
|
|
# ============================================================
|
|
# 10. CREATE SYSTEMD SERVICES
|
|
# ============================================================
|
|
create_systemd_services() {
|
|
info "Creating systemd services..."
|
|
|
|
source /tmp/nexus_install_vars
|
|
|
|
# Create nexus user
|
|
if ! id -u nexus &>/dev/null; then
|
|
useradd -r -s /bin/false -d "${INSTALL_DIR}" nexus >> "$LOG_FILE" 2>&1
|
|
fi
|
|
chown -R nexus:nexus "${INSTALL_DIR}"
|
|
|
|
# POS Service
|
|
cat > /etc/systemd/system/nexus-pos.service << SERVICEEOF
|
|
[Unit]
|
|
Description=Nexus Autoparts POS
|
|
After=network.target postgresql.service redis-server.service
|
|
Wants=postgresql.service redis-server.service
|
|
|
|
[Service]
|
|
Type=notify
|
|
User=nexus
|
|
Group=nexus
|
|
WorkingDirectory=${INSTALL_DIR}/pos
|
|
Environment=PATH=${INSTALL_DIR}/venv/bin:/usr/bin:/bin
|
|
Environment=PYTHONUNBUFFERED=1
|
|
EnvironmentFile=${INSTALL_DIR}/.env
|
|
ExecStartPre=/bin/mkdir -p /var/log/nexus-pos
|
|
ExecStart=${INSTALL_DIR}/venv/bin/gunicorn -c ${INSTALL_DIR}/pos/gunicorn.conf.py "app:create_app()"
|
|
Restart=always
|
|
RestartSec=5
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
SERVICEEOF
|
|
|
|
# Web Dashboard Service
|
|
cat > /etc/systemd/system/nexus-web.service << SERVICEEOF
|
|
[Unit]
|
|
Description=Nexus Autoparts Web Publica
|
|
After=network.target postgresql.service redis-server.service
|
|
Wants=postgresql.service redis-server.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=nexus
|
|
Group=nexus
|
|
WorkingDirectory=${INSTALL_DIR}/dashboard
|
|
Environment=PATH=${INSTALL_DIR}/venv/bin:/usr/bin:/bin
|
|
Environment=PYTHONUNBUFFERED=1
|
|
EnvironmentFile=${INSTALL_DIR}/.env
|
|
ExecStart=${INSTALL_DIR}/venv/bin/python3 server.py
|
|
Restart=always
|
|
RestartSec=5
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
SERVICEEOF
|
|
|
|
# WhatsApp Bridge Service
|
|
cat > /etc/systemd/system/nexus-whatsapp.service << SERVICEEOF
|
|
[Unit]
|
|
Description=Nexus WhatsApp Bridge (Baileys)
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=nexus
|
|
Group=nexus
|
|
WorkingDirectory=${INSTALL_DIR}
|
|
Environment=NODE_ENV=production
|
|
ExecStart=/usr/bin/node ${INSTALL_DIR}/pos/whatsapp-bridge-server.js
|
|
Restart=always
|
|
RestartSec=10
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
SERVICEEOF
|
|
|
|
systemctl daemon-reload >> "$LOG_FILE" 2>&1
|
|
ok "Systemd services created: nexus-pos, nexus-web, nexus-whatsapp"
|
|
}
|
|
|
|
# ============================================================
|
|
# 11. CONFIGURE NGINX
|
|
# ============================================================
|
|
configure_nginx() {
|
|
info "Configuring nginx..."
|
|
|
|
rm -f /etc/nginx/sites-enabled/default 2>/dev/null || true
|
|
|
|
cat > /etc/nginx/sites-available/nexus << 'NGINXEOF'
|
|
# Rate limiting zone
|
|
limit_req_zone $binary_remote_addr zone=pos_login:10m rate=10r/s;
|
|
|
|
upstream nexus_main {
|
|
server 127.0.0.1:5000;
|
|
}
|
|
|
|
upstream nexus_pos {
|
|
server 127.0.0.1:5001;
|
|
}
|
|
|
|
server {
|
|
listen 80;
|
|
server_name _; # Catch-all
|
|
|
|
client_max_body_size 20M;
|
|
|
|
add_header X-Content-Type-Options nosniff always;
|
|
add_header X-Frame-Options SAMEORIGIN always;
|
|
|
|
# Web publica / landing
|
|
location / {
|
|
proxy_pass http://nexus_main;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
}
|
|
|
|
# POS paths
|
|
location /pos/ {
|
|
proxy_pass http://nexus_pos;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
}
|
|
|
|
# Rate limit login endpoint
|
|
location /pos/api/auth/login {
|
|
limit_req zone=pos_login burst=5 nodelay;
|
|
proxy_pass http://nexus_pos;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
}
|
|
|
|
# Health check
|
|
location /health {
|
|
proxy_pass http://nexus_pos/pos/health;
|
|
}
|
|
}
|
|
NGINXEOF
|
|
|
|
ln -sf /etc/nginx/sites-available/nexus /etc/nginx/sites-enabled/nexus
|
|
|
|
if nginx -t >> "$LOG_FILE" 2>&1; then
|
|
ok "Nginx configuration valid."
|
|
else
|
|
err "Nginx configuration test failed. Check $LOG_FILE."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# ============================================================
|
|
# 12. START SERVICES
|
|
# ============================================================
|
|
start_services() {
|
|
info "Starting services..."
|
|
|
|
systemctl enable postgresql >> "$LOG_FILE" 2>&1
|
|
|
|
systemctl enable nginx >> "$LOG_FILE" 2>&1
|
|
systemctl restart nginx >> "$LOG_FILE" 2>&1
|
|
ok "Nginx started."
|
|
|
|
systemctl enable nexus-pos >> "$LOG_FILE" 2>&1
|
|
systemctl start nexus-pos >> "$LOG_FILE" 2>&1
|
|
ok "Nexus POS service started."
|
|
|
|
systemctl enable nexus-web >> "$LOG_FILE" 2>&1
|
|
systemctl start nexus-web >> "$LOG_FILE" 2>&1
|
|
ok "Nexus Web service started."
|
|
|
|
systemctl enable nexus-whatsapp >> "$LOG_FILE" 2>&1
|
|
systemctl start nexus-whatsapp >> "$LOG_FILE" 2>&1
|
|
ok "Nexus WhatsApp bridge started."
|
|
|
|
sleep 3
|
|
|
|
# Verify POS is running
|
|
if systemctl is-active --quiet nexus-pos; then
|
|
ok "All services are running."
|
|
else
|
|
warn "nexus-pos may not have started correctly. Check: journalctl -u nexus-pos"
|
|
fi
|
|
}
|
|
|
|
# ============================================================
|
|
# 13. HEALTH CHECK
|
|
# ============================================================
|
|
run_health_check() {
|
|
info "Running post-installation health check..."
|
|
|
|
source /tmp/nexus_install_vars
|
|
export MASTER_DB_URL="postgresql://${DB_USER}:${DB_PASS}@localhost/${DB_NAME}"
|
|
export TENANT_DB_URL_TEMPLATE="postgresql://${DB_USER}:${DB_PASS}@localhost/{db_name}"
|
|
export DATABASE_URL="${MASTER_DB_URL}"
|
|
|
|
cd "${INSTALL_DIR}"
|
|
"${INSTALL_DIR}/venv/bin/pip" install requests >> "$LOG_FILE" 2>&1 || true
|
|
|
|
if "${INSTALL_DIR}/venv/bin/python3" "${INSTALL_DIR}/scripts/health_check.py"; then
|
|
ok "Health check passed."
|
|
else
|
|
warn "Health check found issues. Review output above."
|
|
fi
|
|
}
|
|
|
|
# ============================================================
|
|
# 14. PRINT SUCCESS
|
|
# ============================================================
|
|
print_success() {
|
|
source /tmp/nexus_install_vars
|
|
|
|
echo ""
|
|
echo -e "${BOLD}${GREEN}"
|
|
echo " ========================================"
|
|
echo " Installation Complete!"
|
|
echo " ========================================"
|
|
echo -e "${NC}"
|
|
echo ""
|
|
echo -e " ${BOLD}POS Login:${NC} http://${ACCESS_HOST}/pos/login"
|
|
echo -e " ${BOLD}Web Catalog:${NC} http://${ACCESS_HOST}/"
|
|
echo -e " ${BOLD}Business:${NC} ${BUSINESS_NAME}"
|
|
echo -e " ${BOLD}Owner:${NC} ${OWNER_NAME}"
|
|
echo -e " ${BOLD}PIN:${NC} ${OWNER_PIN}"
|
|
echo ""
|
|
echo -e " ${BOLD}Services:${NC}"
|
|
echo " POS: systemctl status nexus-pos"
|
|
echo " Web: systemctl status nexus-web"
|
|
echo " WhatsApp: systemctl status nexus-whatsapp"
|
|
echo " Logs: journalctl -u nexus-pos -f"
|
|
echo ""
|
|
echo -e " ${BOLD}Database:${NC}"
|
|
echo " Host: localhost"
|
|
echo " User: ${DB_USER}"
|
|
echo " Pass: ${DB_PASS}"
|
|
echo " Master DB: ${DB_NAME}"
|
|
echo ""
|
|
echo -e " ${BOLD}Files:${NC} ${INSTALL_DIR}/"
|
|
echo -e " ${BOLD}.env:${NC} ${INSTALL_DIR}/.env"
|
|
echo ""
|
|
echo -e " ${YELLOW}Save the database password securely!${NC}"
|
|
echo ""
|
|
}
|
|
|
|
# ============================================================
|
|
# MAIN
|
|
# ============================================================
|
|
main() {
|
|
banner
|
|
mkdir -p "$(dirname "$LOG_FILE")"
|
|
echo "=== Nexus POS Install v2.0 started at $(date) ===" > "$LOG_FILE"
|
|
|
|
check_prerequisites
|
|
install_packages
|
|
configure_postgresql
|
|
setup_app
|
|
interactive_setup
|
|
load_master_schema
|
|
provision_tenant
|
|
apply_migrations
|
|
generate_env
|
|
create_systemd_services
|
|
configure_nginx
|
|
start_services
|
|
run_health_check
|
|
print_success
|
|
|
|
# Cleanup temp vars
|
|
rm -f /tmp/nexus_install_vars
|
|
|
|
log "Installation completed successfully."
|
|
}
|
|
|
|
main "$@"
|