install-linux.sh
Rama main · réplica de lectura del repositorio original.
#!/bin/bash
#═══════════════════════════════════════════════
# Oneirodex Linux Auto-Installer v1.0
# Automated installation script for Linux systems
# Compatible with bash, zsh, and other POSIX shells
#═══════════════════════════════════════════════
# Use bash for advanced features but detect shell for compatibility
if [ -n "${ZSH_VERSION:-}" ]; then
# Running in zsh
setopt SH_WORD_SPLIT # Make zsh behave more like bash for word splitting
setopt BASH_REMATCH # Enable bash-style regex matching
elif [ -n "${BASH_VERSION:-}" ]; then
# Running in bash
set -euo pipefail # Exit on error, undefined vars, pipe failures
else
# Fallback for other POSIX shells
set -eu
fi
# Colors and formatting
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
WHITE='\033[1;37m'
NC='\033[0m' # No Color
# Global variables
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_FILE="$SCRIPT_DIR/install.log"
DISTRO=""
PACKAGE_MANAGER=""
DB_PASSWORD=""
SECRET_KEY=""
GAMES_DIR=""
LIBRARY_ROOTS=""
FORCE_INSTALL=false
DEV_MODE=false
SKIP_DB=false
CUSTOM_PORT="5006"
VERBOSE_MODE=false
INSTALL_STAGE=0
# Cleanup function
cleanup() {
local exit_code=$?
if [ $exit_code -ne 0 ]; then
echo -e "\n${RED}FIELD ALERT // installation did not complete.${NC}"
echo -e "${YELLOW}Field log: $LOG_FILE${NC}"
fi
}
trap cleanup EXIT
# Logging function
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"
}
# Print functions
print_header() {
clear
echo -e "${GREEN}╭──────────────────────────────────────────────────────────────────────╮${NC}"
echo -e "${GREEN}│ Oneirodex // EXPEDITION DECK LINUX │${NC}"
echo -e "${YELLOW}│ QUEST LOG · INSTALLATION ◉ │${NC}"
echo -e "${GREEN}╰──────────────────────────────────────────────────────────────────────╯${NC}"
echo -e "${GREEN}"
cat <<'ONEI'
.-""""-.
/ .--. \
| (o o) |
| ∇ | ONEI
\ ____ / map scout · keeper of the lantern
.-| |-.
/ | [] | \ field kit: d20 · radio · spare key
/___|______|___\
/ \
/______\
ONEI
echo -e "${NC}"
echo -e " A little old-world magic. A well-stocked field terminal."
echo
}
print_step() {
INSTALL_STAGE=$((INSTALL_STAGE + 1))
printf "\n${YELLOW} QUEST %02d // FIELD LOG${NC}\n" "$INSTALL_STAGE"
echo -e "${CYAN} ONEI > $1${NC}"
log "STEP: $1"
}
print_success() {
echo -e "${GREEN} [✓]${NC} $1"
log "SUCCESS: $1"
}
print_error() {
echo -e "${RED} [!]${NC} $1"
log "ERROR: $1"
}
print_warning() {
echo -e "${YELLOW} [!]${NC} $1"
log "WARNING: $1"
}
print_info() {
echo -e "${CYAN} [·]${NC} $1"
log "INFO: $1"
}
# Verbose-only info messages (suppressed in quiet mode)
print_verbose() {
if [ "$VERBOSE_MODE" = true ]; then
echo -e "${CYAN} [·]${NC} $1"
fi
log "VERBOSE: $1"
}
# Execute command with appropriate output handling
execute_quiet() {
local description="$1"
shift
if [ "$VERBOSE_MODE" = true ]; then
# Show full output in verbose mode
"$@"
else
# Suppress stdout but keep stderr in quiet mode
"$@" >/dev/null
fi
}
# Execute command with full output suppression in quiet mode
execute_silent() {
local description="$1"
shift
if [ "$VERBOSE_MODE" = true ]; then
# Show full output in verbose mode
"$@"
else
# Suppress both stdout and stderr in quiet mode
"$@" >/dev/null 2>&1
fi
}
# Check if running as root (bad) or with sudo access (good)
check_permissions() {
# Check if running as root - use $USER and id as EUID may not be available in all shells
if [ "$(id -u)" -eq 0 ]; then
print_error "This script should not be run as root!"
print_info "Please run as a regular user with sudo access:"
print_info " ./install-linux.sh"
exit 1
fi
# Check if sudo is available on the system
if ! command -v sudo >/dev/null 2>&1; then
print_error "sudo command not found"
print_info "Please install sudo or run as root (not recommended)"
exit 1
fi
# First try passwordless sudo check
if sudo -n true 2>/dev/null; then
print_success "Running with appropriate permissions"
return 0
fi
# If passwordless sudo failed, authenticate the user
print_info "This installer requires sudo access to install system packages and configure PostgreSQL"
print_info "You will be prompted for your sudo password..."
if ! sudo -v; then
print_error "sudo authentication failed"
print_info "Please ensure your user account has sudo privileges"
print_info "Contact your system administrator if needed"
exit 1
fi
# Verify sudo is now working
if ! sudo -n true 2>/dev/null; then
print_error "sudo access verification failed after authentication"
exit 1
fi
print_success "Running with appropriate permissions"
}
# Generate secure random password
generate_secure_password() {
if command -v openssl >/dev/null 2>&1; then
openssl rand -base64 32 | tr -d "=+/" | cut -c1-25
elif command -v python3 >/dev/null 2>&1; then
python3 -c "import secrets; print(secrets.token_urlsafe(25))"
else
# Fallback to /dev/urandom
cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 25 | head -n 1
fi
}
# Generate secure secret key
generate_secret_key() {
if command -v python3 >/dev/null 2>&1; then
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
else
# Fallback method
openssl rand -base64 32 | tr -d "=+/"
fi
}
# Parse command line arguments (POSIX compatible)
parse_arguments() {
while [ $# -gt 0 ]; do
case $1 in
--force)
FORCE_INSTALL=true
shift
;;
--dev)
DEV_MODE=true
shift
;;
--no-db)
SKIP_DB=true
shift
;;
--verbose|-v)
VERBOSE_MODE=true
shift
;;
--quiet|-q)
VERBOSE_MODE=false
shift
;;
--games-dir)
if [ $# -lt 2 ]; then
print_error "--games-dir requires an argument"
exit 1
fi
GAMES_DIR="$2"
shift 2
;;
--library-roots)
if [ $# -lt 2 ]; then
print_error "--library-roots requires an argument"
exit 1
fi
LIBRARY_ROOTS="$2"
shift 2
;;
--port)
if [ $# -lt 2 ]; then
print_error "--port requires an argument"
exit 1
fi
CUSTOM_PORT="$2"
shift 2
;;
--help|-h)
show_help
exit 0
;;
*)
print_error "Unknown option: $1"
show_help
exit 1
;;
esac
done
}
# Show help information
show_help() {
echo "Oneirodex Linux Auto-Installer"
echo
echo "USAGE:"
echo " ./install-linux.sh [OPTIONS]"
echo
echo "OPTIONS:"
echo " --force Override existing installation"
echo " --dev Install development dependencies"
echo " --no-db Skip database setup (use existing)"
echo " --verbose, -v Show detailed installation output"
echo " --quiet, -q Suppress detailed output (default)"
echo " --games-dir PATH Specify games directory"
echo " --library-roots Extra scan locations, \"|\"-separated, optional Label="
echo " --port PORT Custom port (default: 5006)"
echo " --help, -h Show this help message"
echo
echo "EXAMPLES:"
echo " ./install-linux.sh"
echo " ./install-linux.sh --verbose"
echo " ./install-linux.sh --games-dir /home/user/games"
echo " ./install-linux.sh --games-dir /srv/games --library-roots 'NAS ROMs=/mnt/nas/roms|Archive=/mnt/archive'"
echo " ./install-linux.sh --force --dev --verbose"
echo
echo "SCAN LOCATIONS:"
echo " Oneirodex scans any path this machine can open, so a NAS share counts"
echo " once the host has mounted it. Mount it first (fstab / systemd automount),"
echo " then pass the mount point to --library-roots. Details:"
echo " docs/runbooks/remote-scan-locations.md"
}
# Backup existing configuration files
backup_existing_config() {
if [ -f "$SCRIPT_DIR/.env" ]; then
print_step "Backing up existing configuration..."
cp "$SCRIPT_DIR/.env" "$SCRIPT_DIR/.env.backup.$(date +%Y%m%d-%H%M%S)"
print_success "Configuration backed up"
fi
}
# Detect Linux distribution
detect_distribution() {
print_step "Detecting Linux distribution..."
if [ -f /etc/os-release ]; then
source /etc/os-release
DISTRO="$ID"
case "$DISTRO" in
ubuntu|debian)
PACKAGE_MANAGER="apt"
print_success "Detected: $PRETTY_NAME (using apt)"
;;
fedora|rhel|centos|rocky|almalinux)
if command -v dnf >/dev/null 2>&1; then
PACKAGE_MANAGER="dnf"
else
PACKAGE_MANAGER="yum"
fi
print_success "Detected: $PRETTY_NAME (using $PACKAGE_MANAGER)"
;;
arch|manjaro)
PACKAGE_MANAGER="pacman"
print_success "Detected: $PRETTY_NAME (using pacman)"
;;
opensuse*|sles)
PACKAGE_MANAGER="zypper"
print_success "Detected: $PRETTY_NAME (using zypper)"
;;
*)
print_warning "Unsupported distribution: $PRETTY_NAME"
print_info "This script supports Ubuntu, Debian, Fedora, RHEL, CentOS, Arch, and openSUSE"
print_info "You may need to install prerequisites manually"
return 1
;;
esac
else
print_error "Cannot detect Linux distribution"
print_info "Please install prerequisites manually and run the application setup"
return 1
fi
}
# Install packages based on distribution
install_package() {
local packages="$1"
case "$PACKAGE_MANAGER" in
apt)
execute_quiet "apt update" sudo apt update -qq
execute_quiet "apt install $packages" sudo apt install -y $packages
;;
dnf)
execute_quiet "dnf install $packages" sudo dnf install -y $packages
;;
yum)
execute_quiet "yum install $packages" sudo yum install -y $packages
;;
pacman)
execute_quiet "pacman install $packages" sudo pacman -Sy --noconfirm $packages
;;
zypper)
execute_quiet "zypper install $packages" sudo zypper install -y $packages
;;
*)
print_error "Unsupported package manager: $PACKAGE_MANAGER"
return 1
;;
esac
}
# Check and install prerequisites
install_prerequisites() {
print_step "Installing prerequisites..."
# Update package lists
case "$PACKAGE_MANAGER" in
apt)
print_verbose "Updating package lists..."
execute_quiet "apt update" sudo apt update -qq
;;
esac
# Define packages per distribution
local python_packages=""
local git_package="git"
local postgresql_packages=""
local build_packages=""
case "$PACKAGE_MANAGER" in
apt)
python_packages="python3 python3-pip python3-venv python3-dev"
postgresql_packages="postgresql postgresql-contrib"
build_packages="build-essential libpq-dev"
;;
dnf|yum)
python_packages="python3 python3-pip python3-devel"
postgresql_packages="postgresql postgresql-server postgresql-contrib"
build_packages="gcc gcc-c++ make postgresql-devel"
;;
pacman)
python_packages="python python-pip python-virtualenv"
postgresql_packages="postgresql"
build_packages="base-devel postgresql-libs"
;;
zypper)
python_packages="python3 python3-pip python3-devel"
postgresql_packages="postgresql postgresql-server postgresql-contrib"
build_packages="gcc gcc-c++ make postgresql-devel"
;;
esac
# Install Python and pip
print_verbose "Installing Python and pip..."
install_package "$python_packages"
# Install git
print_verbose "Installing Git..."
install_package "$git_package"
# Install build dependencies
print_verbose "Installing build dependencies..."
install_package "$build_packages"
# Install PostgreSQL
if [ "$SKIP_DB" != true ]; then
print_verbose "Installing PostgreSQL..."
install_package "$postgresql_packages"
fi
# Verify installations
print_step "Verifying installations..."
if ! command -v python3 >/dev/null 2>&1; then
print_error "Python3 installation failed"
return 1
fi
print_success "Python3 installed: $(python3 --version)"
if ! python3 -m pip --version >/dev/null 2>&1; then
print_error "pip installation failed"
return 1
fi
print_success "pip installed: $(python3 -m pip --version | cut -d' ' -f2)"
if ! command -v git >/dev/null 2>&1; then
print_error "Git installation failed"
return 1
fi
print_success "Git installed: $(git --version | cut -d' ' -f3)"
if [ "$SKIP_DB" != true ]; then
if ! command -v psql >/dev/null 2>&1; then
print_error "PostgreSQL installation failed"
return 1
fi
print_success "PostgreSQL installed"
fi
}
# Get PostgreSQL configuration path based on distribution
get_pg_config_path() {
case "$PACKAGE_MANAGER" in
apt)
# Find the PostgreSQL version directory
if [ -d "/etc/postgresql" ]; then
PG_VERSION=$(ls /etc/postgresql/ 2>/dev/null | head -1)
if [ -n "$PG_VERSION" ]; then
echo "/etc/postgresql/$PG_VERSION/main"
return 0
fi
fi
echo "/etc/postgresql"
;;
dnf|yum)
echo "/var/lib/pgsql/data"
;;
pacman)
echo "/var/lib/postgres/data"
;;
zypper)
echo "/var/lib/pgsql/data"
;;
*)
# Try common locations
for path in "/etc/postgresql" "/var/lib/pgsql/data" "/var/lib/postgres/data"; do
if [ -d "$path" ]; then
echo "$path"
return 0
fi
done
echo "/etc/postgresql"
;;
esac
}
# Configure PostgreSQL authentication for password-based access
configure_postgresql_auth() {
print_step "Configuring PostgreSQL authentication..."
# Get PostgreSQL config path
PG_CONFIG_PATH=$(get_pg_config_path)
PG_HBA_CONF="$PG_CONFIG_PATH/pg_hba.conf"
# Check if pg_hba.conf exists
if [ ! -f "$PG_HBA_CONF" ]; then
print_warning "PostgreSQL pg_hba.conf not found at $PG_HBA_CONF"
print_info "Skipping authentication configuration"
return 0
fi
# Backup original configuration
sudo cp "$PG_HBA_CONF" "$PG_HBA_CONF.backup.$(date +%Y%m%d-%H%M%S)" 2>/dev/null || {
print_warning "Could not backup pg_hba.conf (permissions issue)"
print_info "Continuing without modifying authentication..."
return 0
}
# Add password authentication for the oneirodex database (app role only).
# No rule is added for the `postgres` superuser: a password rule ahead of the
# default `local all postgres peer` line would take over its access to this
# database, and Oneirodex never needs a superuser login.
print_verbose "Adding password authentication for oneirodex database..."
if ! sudo grep -qF '# Added by Oneirodex installer' "$PG_HBA_CONF"; then
# Add our rules at the top (before default rules), once only.
{
echo "# Added by Oneirodex installer - $(date)"
echo "local oneirodex oneirodexuser md5"
echo "host oneirodex oneirodexuser 127.0.0.1/32 md5"
echo "host oneirodex oneirodexuser ::1/128 md5"
echo ""
} | sudo tee "$PG_HBA_CONF.new" >/dev/null
sudo cat "$PG_HBA_CONF" | sudo tee -a "$PG_HBA_CONF.new" >/dev/null
sudo mv "$PG_HBA_CONF.new" "$PG_HBA_CONF"
else
print_info "Oneirodex PostgreSQL authentication rules already exist"
fi
# Reload PostgreSQL configuration
if sudo systemctl reload postgresql 2>/dev/null; then
print_success "PostgreSQL authentication configured and reloaded"
elif sudo service postgresql reload 2>/dev/null; then
print_success "PostgreSQL authentication configured and reloaded"
else
print_warning "Could not reload PostgreSQL - changes will take effect on next restart"
fi
}
# Test database connection with multiple methods
test_database_connection() {
local max_attempts=3
local attempt=1
print_info "Testing database connection..."
while [ $attempt -le $max_attempts ]; do
print_info "Connection attempt $attempt/$max_attempts"
# Method 1: TCP/IP with localhost
if PGPASSWORD="$DB_PASSWORD" psql -h localhost -U oneirodexuser -d oneirodex -c "SELECT 1;" >/dev/null 2>&1; then
print_success "Database connection test successful (localhost TCP/IP)"
return 0
fi
# Method 2: TCP/IP with 127.0.0.1
if PGPASSWORD="$DB_PASSWORD" psql -h 127.0.0.1 -U oneirodexuser -d oneirodex -c "SELECT 1;" >/dev/null 2>&1; then
print_success "Database connection test successful (127.0.0.1 TCP/IP)"
return 0
fi
# Method 3: Create .pgpass file for authentication
if [ "$attempt" -eq $max_attempts ]; then
print_info "Trying .pgpass authentication method..."
echo "localhost:5432:oneirodex:oneirodexuser:$DB_PASSWORD" > ~/.pgpass
chmod 600 ~/.pgpass
if psql -h localhost -U oneirodexuser -d oneirodex -c "SELECT 1;" >/dev/null 2>&1; then
rm ~/.pgpass 2>/dev/null
print_success "Database connection test successful (.pgpass method)"
return 0
fi
rm ~/.pgpass 2>/dev/null
fi
attempt=$((attempt + 1))
if [ $attempt -le $max_attempts ]; then
print_info "Waiting 2 seconds before retry..."
sleep 2
fi
done
# If all methods failed, provide detailed error information
print_error "All database connection methods failed"
print_info "This may be due to PostgreSQL authentication configuration"
print_info "The database and user were created successfully, but connection testing failed"
print_info "You may need to manually configure PostgreSQL authentication"
return 1
}
# Start and configure PostgreSQL
setup_postgresql() {
if [ "$SKIP_DB" = true ]; then
print_info "Skipping PostgreSQL setup (--no-db flag)"
return 0
fi
print_step "Setting up PostgreSQL database..."
# Start PostgreSQL service
case "$PACKAGE_MANAGER" in
apt)
sudo systemctl start postgresql
sudo systemctl enable postgresql
;;
dnf|yum)
# Initialize database if needed
if [ ! -d "/var/lib/pgsql/data/base" ]; then
print_verbose "Initializing PostgreSQL database..."
execute_silent "postgresql setup initdb" sudo postgresql-setup --initdb || true
fi
sudo systemctl start postgresql
sudo systemctl enable postgresql
;;
pacman)
# Initialize database if needed
if [ ! -d "/var/lib/postgres/data/base" ]; then
print_verbose "Initializing PostgreSQL database..."
execute_silent "postgres initdb" sudo -u postgres initdb -D /var/lib/postgres/data
fi
sudo systemctl start postgresql
sudo systemctl enable postgresql
;;
zypper)
sudo systemctl start postgresql
sudo systemctl enable postgresql
;;
esac
# Wait for PostgreSQL to start
print_verbose "Waiting for PostgreSQL to start..."
for i in {1..30}; do
if sudo -u postgres psql -c "SELECT 1;" >/dev/null 2>&1; then
break
fi
sleep 1
done
if ! sudo -u postgres psql -c "SELECT 1;" >/dev/null 2>&1; then
print_error "PostgreSQL failed to start properly"
return 1
fi
print_success "PostgreSQL is running"
# Generate secure password for database user
DB_PASSWORD=$(generate_secure_password)
print_verbose "Creating database and user..."
# Create database user and database
# Only the dedicated app role is created here. This script never sets or
# changes the password of the `postgres` superuser: it keeps whatever
# authentication the host already has (peer over the local socket on most
# distros), and Oneirodex never connects as a superuser.
sudo -u postgres psql << EOF
-- Create user
DO \$\$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'oneirodexuser') THEN
CREATE USER oneirodexuser WITH ENCRYPTED PASSWORD '$DB_PASSWORD';
ELSE
ALTER USER oneirodexuser WITH ENCRYPTED PASSWORD '$DB_PASSWORD';
END IF;
END
\$\$;
-- Create database
SELECT 'CREATE DATABASE oneirodex OWNER oneirodexuser'
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'oneirodex')\gexec
-- Grant privileges
GRANT ALL PRIVILEGES ON DATABASE oneirodex TO oneirodexuser;
GRANT CREATE ON SCHEMA public TO oneirodexuser;
-- Exit without testing connection here (avoids peer auth issues)
\q
EOF
if [ $? -eq 0 ]; then
print_success "Database 'oneirodex' created with user 'oneirodexuser'"
else
print_error "Failed to create database or user"
return 1
fi
# Configure PostgreSQL authentication for password access
configure_postgresql_auth
# Test the database connection with multiple methods
if ! test_database_connection; then
print_warning "Database connection test failed"
print_info "The database and user were created, but connection testing failed"
print_info "This may not prevent Oneirodex from working if the application can connect"
print_info "You can continue with the installation"
# Ask user if they want to continue
printf "Continue with installation? [Y/n]: "
read -r continue_install
if [ "${continue_install:-Y}" != "Y" ] && [ "${continue_install:-Y}" != "y" ]; then
print_error "Installation aborted by user"
return 1
fi
print_info "Continuing with installation..."
fi
}
# Set up Python virtual environment and install dependencies
setup_python_environment() {
print_step "Setting up Python virtual environment..."
# Create virtual environment if it doesn't exist
if [ ! -d "$SCRIPT_DIR/venv" ]; then
python3 -m venv "$SCRIPT_DIR/venv"
print_success "Virtual environment created"
else
print_info "Using existing virtual environment"
fi
# Activate virtual environment and install dependencies
source "$SCRIPT_DIR/venv/bin/activate"
print_verbose "Installing Python dependencies..."
if execute_silent "pip install requirements" python3 -m pip install -r "$SCRIPT_DIR/requirements.txt"; then
print_success "Python dependencies installed"
else
print_error "Failed to install Python dependencies"
return 1
fi
if [ "$DEV_MODE" = true ]; then
print_verbose "Installing development dependencies..."
if [ -f "$SCRIPT_DIR/requirements-dev.txt" ]; then
execute_silent "pip install dev requirements" python3 -m pip install -r "$SCRIPT_DIR/requirements-dev.txt"
fi
fi
}
# Configure application settings
configure_application() {
print_step "Configuring Oneirodex application..."
# Keep the tracked config.py as the source of truth and preserve the
# signing key across reinstalls.
SECRET_KEY=""
if [ -f "$SCRIPT_DIR/.env" ]; then
SECRET_KEY=$(sed -n 's/^SECRET_KEY=//p' "$SCRIPT_DIR/.env" | head -n 1)
fi
if [ -z "$SECRET_KEY" ]; then
SECRET_KEY=$(generate_secret_key)
fi
# Prompt for games directory if not specified
if [ -z "$GAMES_DIR" ]; then
echo
print_info "Please specify the directory containing your game files:"
read -p "Games directory path [/home/$USER/games]: " input_dir
GAMES_DIR="${input_dir:-/home/$USER/games}"
fi
# Validate games directory
if [ ! -d "$GAMES_DIR" ]; then
print_warning "Games directory does not exist: $GAMES_DIR"
read -p "Create this directory? [Y/n]: " create_dir
case "${create_dir:-Y}" in
[Yy]|[Yy][Ee][Ss]) create_it=true ;;
*) create_it=false ;;
esac
if [ "$create_it" = true ]; then
mkdir -p "$GAMES_DIR"
print_success "Games directory created: $GAMES_DIR"
else
print_warning "You'll need to update the games directory later in .env"
fi
else
print_success "Games directory exists: $GAMES_DIR"
fi
# Extra scan locations. Anything already mounted on this machine qualifies —
# a NAS share under /mnt, a second disk, an external drive. Mounting is the
# operator's job; this only records where Oneirodex should look.
if [ -z "$LIBRARY_ROOTS" ]; then
echo
print_info "Extra scan locations beyond $GAMES_DIR (optional)."
print_info "Separate with '|', label with 'Name=': 'NAS ROMs=/mnt/nas/roms|/mnt/archive'"
read -p "Extra scan locations [none]: " input_roots
LIBRARY_ROOTS="${input_roots:-}"
fi
if [ -n "$LIBRARY_ROOTS" ]; then
# Warn, do not fail: an automount can be legitimately absent at install
# time and appear on first access.
OLD_IFS="$IFS"
IFS='|'
for entry in $LIBRARY_ROOTS; do
root_path="${entry#*=}"
root_path="$(echo "$root_path" | sed 's/^ *//; s/ *$//')"
if [ -n "$root_path" ] && [ ! -d "$root_path" ]; then
print_warning "Scan location not mounted yet: $root_path"
elif [ -n "$root_path" ]; then
print_success "Scan location found: $root_path"
fi
done
IFS="$OLD_IFS"
fi
# Create .env file
print_verbose "Creating environment configuration..."
cat > "$SCRIPT_DIR/.env" << EOF
# Oneirodex Configuration - Generated by auto-installer $(date)
# Database connection
DATABASE_URL=postgresql://oneirodexuser:$DB_PASSWORD@localhost:5432/oneirodex
# Test database (only needed if running unit tests)
TEST_DATABASE_URL=postgresql://oneirodexuser:$DB_PASSWORD@localhost:5432/oneirodextest
# Game files directory
DATA_FOLDER_GAMES=$GAMES_DIR
# Extra scan locations: NAS shares, second disks, anything else this machine
# has mounted. "|"-separated, optional "Label=" prefix. The share must already
# be mounted here — see docs/runbooks/remote-scan-locations.md
ONEIRODEX_LIBRARY_ROOTS=$LIBRARY_ROOTS
# Base folders for path resolution
BASE_FOLDER_WINDOWS=C:\\
BASE_FOLDER_POSIX=/
# Flask secret key (keep this secure!)
SECRET_KEY=$SECRET_KEY
# Upload directory for cover images and zips
UPLOAD_FOLDER=$SCRIPT_DIR/oneirodex/static/library
# Development mode
DEV_MODE=${DEV_MODE:-false}
EOF
# Set secure permissions on .env file
chmod 600 "$SCRIPT_DIR/.env"
print_success "Environment configuration created"
# Make all shell scripts executable (required after git clone)
print_verbose "Making all shell scripts executable..."
chmod +x "$SCRIPT_DIR"/*.sh
print_success "Shell script permissions set"
}
# Validate the installation
validate_installation() {
print_step "Validating installation..."
# Check if virtual environment exists and works
if [ -d "$SCRIPT_DIR/venv" ]; then
source "$SCRIPT_DIR/venv/bin/activate"
# Test Flask app creation
if python3 -c "from oneirodex import create_app; app = create_app(); print('Flask app creation: OK')" >/dev/null 2>&1; then
print_success "Flask application setup validated"
else
print_error "Flask application validation failed"
return 1
fi
else
print_error "Virtual environment not found"
return 1
fi
# Check database connection
if [ "$SKIP_DB" != true ]; then
if test_database_connection >/dev/null 2>&1; then
print_success "Database connection validated"
else
print_warning "Database connection validation failed"
print_info "This may not prevent Oneirodex from functioning"
fi
fi
# Check required files
for file in ".env" "config.py" "startweb.sh" "requirements.txt"; do
if [ -f "$SCRIPT_DIR/$file" ]; then
print_success "Required file exists: $file"
else
print_error "Missing required file: $file"
return 1
fi
done
}
# Show installation summary
show_summary() {
echo
echo -e "${GREEN}╭──────────────────────────────────────────────────────────────────────╮${NC}"
echo -e "${GREEN}│ QUEST COMPLETE // THE DECK IS READY │${NC}"
echo -e "${GREEN}╰──────────────────────────────────────────────────────────────────────╯${NC}"
echo -e "${CYAN} ONEI > Your worlds are waiting at the gate.${NC}"
echo
echo -e "${CYAN} Launch gate:${NC} http://localhost:$CUSTOM_PORT"
echo -e "${CYAN} Game cache:${NC} $GAMES_DIR"
if [ -n "$LIBRARY_ROOTS" ]; then
echo -e "${CYAN} Trail markers:${NC} $LIBRARY_ROOTS"
fi
if [ "$SKIP_DB" != true ]; then
echo -e "${CYAN} Camp ledger:${NC} oneirodex (credentials stored in .env)"
fi
echo -e "${CYAN} Start command:${NC} ./startweb.sh"
echo -e "${CYAN} Stop:${NC} Press Ctrl+C"
echo -e "${CYAN} Reset database:${NC} ./startweb.sh --force-setup"
echo -e "${CYAN} Field log:${NC} $LOG_FILE"
echo
# Ask if user wants to start the application
read -p "Start Oneirodex now? [Y/n]: " start_now
case "${start_now:-Y}" in
[Yy]|[Yy][Ee][Ss])
start_it=true
;;
*)
start_it=false
;;
esac
if [ "$start_it" = true ]; then
echo
print_info "Opening the launch gate..."
print_info "Open your browser to http://localhost:$CUSTOM_PORT when ready"
print_info "Press Ctrl+C to stop the application"
echo
# Export PORT for startweb.sh
export PORT="$CUSTOM_PORT"
exec ./startweb.sh
else
echo
print_info "To start Oneirodex later, run: ./startweb.sh"
print_info "Then open your browser to: http://localhost:$CUSTOM_PORT"
fi
}
# Main installation function
main() {
# Initialize log file
echo "Oneirodex Linux Auto-Installer - $(date)" > "$LOG_FILE"
print_header
# Parse command line arguments
parse_arguments "$@"
# Show what the installer will do
print_info "This installer will:"
print_info " • Install system packages (Python, PostgreSQL, build tools)"
print_info " • Configure PostgreSQL database"
print_info " • Set up Python virtual environment"
print_info " • Configure Oneirodex application"
echo
print_info "Administrative privileges are required for system package installation."
# Show verbosity mode
if [ "$VERBOSE_MODE" = true ]; then
print_info "Running in verbose mode - showing all installation details"
else
print_info "Running in quiet mode - use --verbose to see detailed output"
fi
echo
print_info "Press any key to continue or Ctrl+C to quit..."
read -n 1 -s -r
# Check permissions
check_permissions
# Detect distribution
if ! detect_distribution; then
exit 1
fi
# Check if already in Oneirodex directory
if [ ! -f "$SCRIPT_DIR/startweb.sh" ] || [ ! -f "$SCRIPT_DIR/requirements.txt" ]; then
print_error "This script must be run from the Oneirodex directory"
print_info "Please clone the repository first:"
print_info " git clone https://github.com/cephyrixzyth/oneirodex.git"
print_info " cd oneirodex"
print_info " ./install-linux.sh"
exit 1
fi
# Backup existing configuration
backup_existing_config
# Install prerequisites
install_prerequisites
# Setup PostgreSQL
if ! setup_postgresql; then
exit 1
fi
# Setup Python environment
if ! setup_python_environment; then
exit 1
fi
# Configure application
if ! configure_application; then
exit 1
fi
# Validate installation
if ! validate_installation; then
exit 1
fi
# Show summary and optionally start
show_summary
}
# Run main function if script is executed directly
if [ "${0##*/}" = "install-linux.sh" ] || [ "${0}" = "./install-linux.sh" ]; then
main "$@"
fi