Repository navigation
Expand file tree
/
Copy pathinstall-linux.sh
More file actions
1051 lines (920 loc) · 34.3 KB
/
Copy pathinstall-linux.sh
File metadata and controls
1051 lines (920 loc) · 34.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/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