Repository navigation
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·961 lines (818 loc) · 35.1 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·961 lines (818 loc) · 35.1 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
#!/bin/bash
#
# Client St0r Installer - Simple automated setup
# Usage: bash install.sh
#
set -e # Exit on error
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}"
echo "╔═══════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ Client St0r Automated Installer ║"
echo "║ v2.2.0 ║"
echo "║ ║"
echo "╚═══════════════════════════════════════════════════════════╝"
echo -e "${NC}"
# Function to print status messages
print_status() {
echo -e "${GREEN}[✓]${NC} $1"
}
print_error() {
echo -e "${RED}[✗]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[!]${NC} $1"
}
print_info() {
echo -e "${BLUE}[i]${NC} $1"
}
# Check if running as root
if [ "$EUID" -eq 0 ]; then
print_error "Please do not run this script as root. It will ask for sudo when needed."
exit 1
fi
# Get the directory where the script is located
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# Try to find the actual installation directory
INSTALL_DIR="$SCRIPT_DIR"
# Detect Python version early (needed for both install and upgrade paths)
PYTHON_VERSION=""
if command -v python3.12 &> /dev/null; then
PYTHON_VERSION="3.12"
elif command -v python3.13 &> /dev/null; then
PYTHON_VERSION="3.13"
elif command -v python3.11 &> /dev/null; then
PYTHON_VERSION="3.11"
else
# Will be detected later in Step 1
PYTHON_VERSION="3.12" # Default assumption, will be updated
fi
# Check if current directory has venv/.env
if [ ! -f "$SCRIPT_DIR/venv/bin/activate" ] && [ ! -f "$SCRIPT_DIR/.env" ]; then
# Look for installation in common locations
POSSIBLE_DIRS=(
"$HOME/clientst0r"
"$(dirname "$SCRIPT_DIR")"
"/home/administrator/clientst0r"
)
for dir in "${POSSIBLE_DIRS[@]}"; do
if [ -f "$dir/venv/bin/activate" ] || [ -f "$dir/.env" ]; then
INSTALL_DIR="$dir"
print_warning "Found existing installation at: $INSTALL_DIR"
break
fi
done
fi
cd "$INSTALL_DIR"
print_info "Installing from: $INSTALL_DIR"
echo ""
# Detect existing installation
EXISTING_INSTALL=false
HAS_DATABASE=false
HAS_VENV=false
HAS_SERVICE=false
HAS_ENV=false
# Check for database with data
if sudo mysql -e "USE clientst0r; SELECT COUNT(*) FROM auth_user;" 2>/dev/null | grep -q -E "[0-9]+"; then
HAS_DATABASE=true
EXISTING_INSTALL=true
fi
# Check for venv
if [ -f "$INSTALL_DIR/venv/bin/activate" ]; then
HAS_VENV=true
EXISTING_INSTALL=true
fi
# Check for service
if sudo systemctl list-unit-files | grep -q clientst0r-gunicorn.service; then
HAS_SERVICE=true
EXISTING_INSTALL=true
fi
# Check for .env
if [ -f "$INSTALL_DIR/.env" ]; then
HAS_ENV=true
EXISTING_INSTALL=true
fi
if [ "$EXISTING_INSTALL" = true ]; then
echo ""
print_warning "Existing Client St0r installation detected!"
echo ""
# Show what exists
if [ "$HAS_ENV" = true ]; then
echo " • Found: .env configuration file"
fi
if [ "$HAS_VENV" = true ]; then
echo " • Found: Python virtual environment"
fi
if [ "$HAS_SERVICE" = true ]; then
echo " • Found: systemd service"
if sudo systemctl is-active --quiet clientst0r-gunicorn.service; then
echo " Status: Running ✓"
else
echo " Status: Stopped"
fi
fi
if [ "$HAS_DATABASE" = true ]; then
USER_COUNT=$(sudo mysql -e "USE clientst0r; SELECT COUNT(*) FROM auth_user;" 2>/dev/null | tail -n1)
echo " • Found: Database 'clientst0r' with $USER_COUNT user(s)"
fi
echo ""
echo "What would you like to do?"
echo ""
echo " 1) Upgrade/Update (pull latest code, run migrations, restart service)"
echo " 2) System Check (verify all components are working)"
echo " 3) Clean Install (remove everything and reinstall)"
echo " 4) Exit"
echo ""
read -p "Enter choice [1-4]: " choice
case $choice in
1)
print_info "Starting upgrade process..."
# Check for .env file
if [ ! -f "$INSTALL_DIR/.env" ]; then
print_error ".env file not found!"
print_error "The upgrade requires an existing .env configuration file."
echo ""
echo "Please restore your .env file or use option 3 (Clean Install)"
exit 1
fi
# Verify .env has required keys
if ! grep -q "SECRET_KEY" "$INSTALL_DIR/.env"; then
print_error ".env file is missing SECRET_KEY!"
print_error "Your .env file is corrupted or incomplete."
echo ""
echo "You need to restore a backup .env file or regenerate it."
echo "Backup file location: $INSTALL_DIR/.env.backup"
exit 1
fi
print_status "Configuration file verified"
# Stop service and kill any gunicorn processes
print_info "Stopping service..."
sudo systemctl stop clientst0r-gunicorn.service 2>/dev/null || true
sudo pkill -9 gunicorn 2>/dev/null || true
sleep 1
# Pull latest code
print_info "Pulling latest code from GitHub..."
cd "$INSTALL_DIR"
git pull origin main || print_warning "Git pull failed or not a git repository"
# Check if venv exists, create if missing
if [ ! -d "venv" ] || [ ! -f "venv/bin/activate" ]; then
print_warning "Virtual environment not found, creating..."
# Check write permissions
if [ ! -w "$INSTALL_DIR" ]; then
print_error "No write permission in $INSTALL_DIR"
print_error "The directory is owned by: $(stat -c '%U:%G' "$INSTALL_DIR")"
print_error "Current user: $USER"
echo ""
echo "Fix permissions with:"
echo " sudo chown -R $USER:$USER $INSTALL_DIR"
exit 1
fi
python${PYTHON_VERSION} -m venv venv
if [ ! -f "venv/bin/activate" ]; then
print_error "Failed to create virtual environment"
print_error "Install python${PYTHON_VERSION}-venv: sudo apt-get install python${PYTHON_VERSION}-venv"
exit 1
fi
print_status "Virtual environment created"
fi
# Activate venv and upgrade dependencies
print_info "Updating Python dependencies..."
source venv/bin/activate
pip install --upgrade pip -q
pip install -r requirements.txt --upgrade --progress-bar on
# Check and install Snyk CLI if missing
print_info "Checking for Snyk CLI..."
if ! command -v snyk &> /dev/null; then
print_warning "Snyk CLI not found, attempting installation..."
if command -v npm &> /dev/null; then
sudo npm install -g snyk --silent || print_warning "Failed to install Snyk CLI (optional)"
# Create symlink if snyk is in NVM but not in system PATH
if ! command -v snyk &> /dev/null && [ -d "$HOME/.nvm/versions/node" ]; then
SNYK_PATH=$(find "$HOME/.nvm/versions/node/" -name snyk -type f 2>/dev/null | head -1)
if [ -n "$SNYK_PATH" ] && [ -f "$SNYK_PATH" ]; then
print_info "Creating system-wide symlink for Snyk CLI..."
sudo ln -sf "$SNYK_PATH" /usr/local/bin/snyk && print_status "Snyk CLI linked to /usr/local/bin/snyk"
fi
fi
if command -v snyk &> /dev/null; then
print_status "Snyk CLI installed successfully"
else
print_warning "Snyk CLI installation failed - security scanning will be unavailable"
fi
else
print_warning "npm not available - skipping Snyk CLI installation"
print_warning "Install with: sudo apt-get install nodejs npm && sudo npm install -g snyk"
fi
else
print_status "Snyk CLI already installed"
fi
# Run migrations (makemigrations first to catch any missing migration files)
print_info "Running database migrations..."
python3 manage.py makemigrations --no-input 2>/dev/null || true
python3 manage.py migrate
# Collect static files
print_info "Collecting static files..."
python3 manage.py collectstatic --noinput
# Recreate service file if missing (handles older installs or renamed service)
if ! sudo systemctl list-unit-files | grep -q clientst0r-gunicorn.service; then
print_info "Service file not found, creating it..."
sudo tee /etc/systemd/system/clientst0r-gunicorn.service > /dev/null << 'SVCEOF'
[Unit]
Description=Client St0r Gunicorn
After=network.target mariadb.service
Wants=mariadb.service
[Service]
Type=simple
User=USER_PLACEHOLDER
Group=USER_PLACEHOLDER
WorkingDirectory=WORKDIR_PLACEHOLDER
Environment="PATH=WORKDIR_PLACEHOLDER/venv/bin"
ExecStart=WORKDIR_PLACEHOLDER/venv/bin/gunicorn \
--workers 4 \
--bind 0.0.0.0:8000 \
--timeout 120 \
--access-logfile /var/log/itdocs/gunicorn-access.log \
--error-logfile /var/log/itdocs/gunicorn-error.log \
--log-level info \
config.wsgi:application
Restart=always
[Install]
WantedBy=multi-user.target
SVCEOF
sudo sed -i "s|USER_PLACEHOLDER|$USER|g" /etc/systemd/system/clientst0r-gunicorn.service
sudo sed -i "s|WORKDIR_PLACEHOLDER|$INSTALL_DIR|g" /etc/systemd/system/clientst0r-gunicorn.service
sudo systemctl daemon-reload
sudo systemctl enable clientst0r-gunicorn.service 2>&1 | grep -v "Created symlink" || true
print_status "Service file created"
fi
# Restart service
print_info "Restarting service..."
sudo systemctl start clientst0r-gunicorn.service
# Check status
sleep 2
if sudo systemctl is-active --quiet clientst0r-gunicorn.service; then
print_status "Upgrade complete! Service is running."
SERVER_IP=$(hostname -I | awk '{print $1}')
echo ""
echo "Access at: http://${SERVER_IP}:8000"
else
print_error "Service failed to start. Check logs:"
echo " sudo journalctl -u clientst0r-gunicorn.service -n 50"
fi
exit 0
;;
2)
print_info "Running system check..."
echo ""
# Check Python
if [ -f "$INSTALL_DIR/venv/bin/python3" ]; then
PYTHON_VERSION=$($INSTALL_DIR/venv/bin/python3 --version 2>&1)
print_status "Python: $PYTHON_VERSION"
else
print_error "Python virtual environment not found"
fi
# Check database
if sudo mysql -e "SHOW DATABASES LIKE 'clientst0r';" 2>/dev/null | grep -q clientst0r; then
print_status "Database: clientst0r exists"
TABLE_COUNT=$(sudo mysql -e "USE clientst0r; SHOW TABLES;" 2>/dev/null | wc -l)
echo " Tables: $((TABLE_COUNT - 1))"
else
print_error "Database: clientst0r not found"
fi
# Check service
if sudo systemctl is-active --quiet clientst0r-gunicorn.service; then
print_status "Service: Running"
echo " $(sudo systemctl show clientst0r-gunicorn.service -p MainPID --value) - $(sudo systemctl show clientst0r-gunicorn.service -p ActiveState --value)"
elif sudo systemctl list-unit-files | grep -q clientst0r-gunicorn.service; then
print_warning "Service: Installed but not running"
echo " Start with: sudo systemctl start clientst0r-gunicorn.service"
else
print_error "Service: Not installed"
fi
# Check port
if ss -tlnp 2>/dev/null | grep -q :8000; then
print_status "Port 8000: Listening"
else
print_warning "Port 8000: Not listening"
fi
# Check logs directory
if [ -d "/var/log/itdocs" ]; then
print_status "Log directory: /var/log/itdocs"
else
print_error "Log directory not found"
fi
# Test HTTP response
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/ 2>/dev/null || echo "000")
if [ "$HTTP_CODE" = "302" ] || [ "$HTTP_CODE" = "200" ]; then
print_status "HTTP Response: $HTTP_CODE (OK)"
SERVER_IP=$(hostname -I | awk '{print $1}')
echo ""
echo "Access at: http://${SERVER_IP}:8000"
else
print_error "HTTP Response: $HTTP_CODE (Failed)"
fi
echo ""
print_info "System check complete"
exit 0
;;
3)
print_warning "This will DELETE all data and reinstall from scratch!"
read -p "Are you sure? Type 'yes' to confirm: " confirm
if [ "$confirm" != "yes" ]; then
echo "Aborted."
exit 0
fi
print_info "Removing existing installation..."
# Stop and remove service
sudo systemctl stop clientst0r-gunicorn.service 2>/dev/null || true
sudo systemctl disable clientst0r-gunicorn.service 2>/dev/null || true
sudo rm -f /etc/systemd/system/clientst0r-gunicorn.service
sudo systemctl daemon-reload
# Drop database
print_info "Dropping database..."
sudo mysql << 'EOSQL'
DROP DATABASE IF EXISTS clientst0r;
DROP USER IF EXISTS 'clientst0r'@'localhost';
FLUSH PRIVILEGES;
EOSQL
# Remove directories
cd ~
rm -rf "$INSTALL_DIR/venv"
rm -f "$INSTALL_DIR/.env"
rm -f "$INSTALL_DIR/.env.backup"
# Also clean up any stale files in home directory that might confuse auto-detection
rm -rf ~/venv
rm -f ~/.env ~/.env.backup
sudo rm -rf /var/log/itdocs/
print_status "Cleanup complete. Starting fresh installation..."
echo ""
# Reset INSTALL_DIR to where the script actually is (ignore auto-detection)
INSTALL_DIR="$SCRIPT_DIR"
# Change to the correct install directory
cd "$INSTALL_DIR"
print_info "Installing to: $INSTALL_DIR"
# Continue to normal installation
;;
4)
echo "Exiting."
exit 0
;;
*)
print_error "Invalid choice. Exiting."
exit 1
;;
esac
fi
# Step 1: Check and install system prerequisites
print_info "Step 1/11: Checking system prerequisites..."
PACKAGES_TO_INSTALL=()
# Detect Python version (prefer 3.12, fallback to 3.13, 3.11)
PYTHON_VERSION=""
if command -v python3.12 &> /dev/null; then
PYTHON_VERSION="3.12"
print_status "Found Python 3.12"
elif command -v python3.13 &> /dev/null; then
PYTHON_VERSION="3.13"
print_status "Found Python 3.13"
elif command -v python3.11 &> /dev/null; then
PYTHON_VERSION="3.11"
print_status "Found Python 3.11"
else
# Try to detect which version is available in repos
print_warning "No suitable Python version found, detecting available versions..."
if apt-cache show python3.13 &> /dev/null; then
PYTHON_VERSION="3.13"
print_info "Python 3.13 available in repositories"
PACKAGES_TO_INSTALL+=("python3.13")
elif apt-cache show python3.12 &> /dev/null; then
PYTHON_VERSION="3.12"
print_info "Python 3.12 available in repositories"
PACKAGES_TO_INSTALL+=("python3.12")
elif apt-cache show python3.11 &> /dev/null; then
PYTHON_VERSION="3.11"
print_info "Python 3.11 available in repositories"
PACKAGES_TO_INSTALL+=("python3.11")
else
print_error "No compatible Python version (3.11, 3.12, or 3.13) found!"
print_error "Please install Python 3.11 or later:"
echo " sudo apt-get install python3.12 python3.12-venv python3.12-dev"
exit 1
fi
fi
print_info "Using Python ${PYTHON_VERSION}"
# Check for python3-venv
if ! dpkg -l | grep -q python${PYTHON_VERSION}-venv; then
print_warning "python${PYTHON_VERSION}-venv not found"
PACKAGES_TO_INSTALL+=("python${PYTHON_VERSION}-venv")
fi
# Check for pip
if ! command -v pip3 &> /dev/null; then
print_warning "pip not found"
PACKAGES_TO_INSTALL+=("python3-pip")
fi
# Check for MySQL/MariaDB server
if ! systemctl list-unit-files | grep -q 'mariadb.service\|mysql.service'; then
print_warning "MariaDB server not found"
PACKAGES_TO_INSTALL+=("mariadb-server")
fi
# Check for MySQL/MariaDB client
if ! command -v mysql &> /dev/null; then
print_warning "MySQL client not found"
PACKAGES_TO_INSTALL+=("mariadb-client")
fi
# Check for build essentials (needed for Python packages like cryptography, mysqlclient)
if ! dpkg -l | grep -q build-essential; then
print_warning "build-essential not found (needed for Python packages)"
PACKAGES_TO_INSTALL+=("build-essential")
fi
if ! dpkg -l | grep -q python${PYTHON_VERSION}-dev; then
PACKAGES_TO_INSTALL+=("python${PYTHON_VERSION}-dev")
fi
if ! dpkg -l | grep -q libssl-dev; then
PACKAGES_TO_INSTALL+=("libssl-dev")
fi
if ! dpkg -l | grep -q libffi-dev; then
PACKAGES_TO_INSTALL+=("libffi-dev")
fi
# Check for pkg-config (needed for mysqlclient)
if ! command -v pkg-config &> /dev/null; then
print_warning "pkg-config not found (needed for mysqlclient)"
PACKAGES_TO_INSTALL+=("pkg-config")
fi
# Check for MySQL development headers (needed for mysqlclient Python package)
if ! dpkg -l | grep -q libmysqlclient-dev && ! dpkg -l | grep -q default-libmysqlclient-dev; then
print_warning "MySQL development headers not found (needed for mysqlclient)"
PACKAGES_TO_INSTALL+=("default-libmysqlclient-dev")
fi
# Check for LDAP development headers (needed for python-ldap package)
if ! dpkg -l | grep -q libldap2-dev && ! dpkg -l | grep -q libldap-dev; then
print_warning "LDAP development headers not found (needed for python-ldap)"
PACKAGES_TO_INSTALL+=("libldap2-dev" "libsasl2-dev")
fi
# Check for Node.js (needed for Snyk and other tools)
if ! command -v node &> /dev/null; then
print_warning "Node.js not found (needed for Snyk security scanning)"
PACKAGES_TO_INSTALL+=("nodejs" "npm")
fi
# Install missing packages
if [ ${#PACKAGES_TO_INSTALL[@]} -gt 0 ]; then
print_info "Installing missing packages: ${PACKAGES_TO_INSTALL[*]}"
echo -e "${YELLOW}This requires sudo privileges. You may be prompted for your password.${NC}"
# Update package list
print_info "Updating package list..."
sudo apt-get update -qq
# Set DEBIAN_FRONTEND to avoid interactive prompts
print_info "Installing packages (this may take a few minutes)..."
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y "${PACKAGES_TO_INSTALL[@]}"
print_status "System prerequisites installed"
else
print_status "All system prerequisites met"
fi
# Step 2: Create virtual environment
print_info "Step 2/11: Creating Python virtual environment..."
if [ -d "venv" ]; then
print_warning "Virtual environment already exists, removing old one..."
rm -rf venv
fi
python${PYTHON_VERSION} -m venv venv
if [ ! -f "venv/bin/activate" ]; then
print_error "Failed to create virtual environment"
print_error "Python venv creation failed. Is python${PYTHON_VERSION}-venv installed?"
exit 1
fi
print_status "Virtual environment created with Python ${PYTHON_VERSION}"
# Step 3: Activate virtual environment and install dependencies
print_info "Step 3/11: Installing Python dependencies (this may take 2-3 minutes)..."
source venv/bin/activate
if [ -z "$VIRTUAL_ENV" ]; then
print_error "Failed to activate virtual environment"
exit 1
fi
echo -e "${YELLOW}Upgrading pip...${NC}"
pip install --upgrade pip -q
echo -e "${YELLOW}Installing dependencies from requirements.txt...${NC}"
echo -e "${YELLOW}Please wait, this will take a few minutes...${NC}"
pip install -r requirements.txt --progress-bar on
# Verify critical packages were installed
if [ ! -f "venv/bin/gunicorn" ]; then
print_error "Failed to install dependencies - gunicorn not found"
print_error "Check the pip install output above for errors"
exit 1
fi
print_status "Python dependencies installed"
# Install Snyk CLI for security scanning
print_info "Installing Snyk CLI for security scanning..."
if command -v npm &> /dev/null; then
if ! command -v snyk &> /dev/null; then
sudo npm install -g snyk --silent || print_warning "Failed to install Snyk CLI (optional)"
# Create symlink if snyk is in NVM but not in system PATH
if ! command -v snyk &> /dev/null && [ -d "$HOME/.nvm/versions/node" ]; then
SNYK_PATH=$(find "$HOME/.nvm/versions/node/" -name snyk -type f 2>/dev/null | head -1)
if [ -n "$SNYK_PATH" ] && [ -f "$SNYK_PATH" ]; then
print_info "Creating system-wide symlink for Snyk CLI..."
sudo ln -sf "$SNYK_PATH" /usr/local/bin/snyk && print_status "Snyk CLI linked to /usr/local/bin/snyk"
fi
fi
if command -v snyk &> /dev/null; then
print_status "Snyk CLI installed"
else
print_warning "Snyk CLI installation failed - security scanning will be unavailable"
fi
else
print_status "Snyk CLI already installed"
fi
else
print_warning "npm not available - skipping Snyk CLI installation"
print_warning "Install with: sudo apt-get install nodejs npm && sudo npm install -g snyk"
fi
# Step 4: Generate secrets
print_info "Step 4/11: Generating secure secrets..."
APP_MASTER_KEY=$(python3 -c "import base64, os; print(base64.b64encode(os.urandom(32)).decode())")
SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_urlsafe(50))")
API_KEY_SECRET=$(python3 -c "import secrets; print(secrets.token_urlsafe(50))")
# Generate random secure database password (32 chars, alphanumeric + special chars)
DB_PASSWORD=$(python3 -c "import secrets; import string; chars = string.ascii_letters + string.digits + '!@#$%^&*'; print(''.join(secrets.choice(chars) for _ in range(32)))")
print_status "Secrets generated"
# Step 5: Create .env file
print_info "Step 5/11: Creating environment configuration..."
if [ -f ".env" ]; then
print_warning ".env file already exists, backing up to .env.backup"
cp .env .env.backup
fi
cat > .env << EOF
# Django Settings
DEBUG=True
SECRET_KEY=${SECRET_KEY}
ALLOWED_HOSTS=localhost,127.0.0.1
# Database (MariaDB/MySQL)
DB_NAME=clientst0r
DB_USER=clientst0r
DB_PASSWORD=${DB_PASSWORD}
DB_HOST=localhost
DB_PORT=3306
# Encryption
APP_MASTER_KEY=${APP_MASTER_KEY}
API_KEY_SECRET=${API_KEY_SECRET}
# Email (Optional - configure later)
EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=
EMAIL_HOST_PASSWORD=
# Application
SITE_NAME=Client St0r
SITE_URL=http://localhost:8000
# Security
REQUIRE_2FA=False
EOF
print_status "Environment file created (.env)"
print_info "Secure random database password generated"
# Step 5.5: Generate sudoers files with correct paths
echo ""
print_info "Step 5.5/11: Generating sudoers files..."
# Create deploy directory if it doesn't exist
mkdir -p "$INSTALL_DIR/deploy"
# Generate clientst0r-install-sudoers
cat > "$INSTALL_DIR/deploy/clientst0r-install-sudoers" <<SUDOEOF
# Sudoers configuration for Client St0r automatic fail2ban installation
# Install: sudo cp $INSTALL_DIR/deploy/clientst0r-install-sudoers /etc/sudoers.d/clientst0r-install
# Permissions: sudo chmod 0440 /etc/sudoers.d/clientst0r-install
# Allow $USER user to install and configure fail2ban without password
$USER ALL=(ALL) NOPASSWD: /usr/bin/apt-get update
$USER ALL=(ALL) NOPASSWD: /usr/bin/apt-get install -y fail2ban
$USER ALL=(ALL) NOPASSWD: /bin/systemctl enable fail2ban
$USER ALL=(ALL) NOPASSWD: /bin/systemctl start fail2ban
$USER ALL=(ALL) NOPASSWD: /bin/systemctl status fail2ban
$USER ALL=(ALL) NOPASSWD: /bin/cp $INSTALL_DIR/deploy/clientst0r-fail2ban-sudoers /etc/sudoers.d/clientst0r-fail2ban
$USER ALL=(ALL) NOPASSWD: /bin/chmod 0440 /etc/sudoers.d/clientst0r-fail2ban
SUDOEOF
# Generate clientst0r-fail2ban-sudoers
cat > "$INSTALL_DIR/deploy/clientst0r-fail2ban-sudoers" <<FBSUDOEOF
# Sudoers configuration for Client St0r fail2ban integration
# Install: sudo cp $INSTALL_DIR/deploy/clientst0r-fail2ban-sudoers /etc/sudoers.d/clientst0r-fail2ban
# Permissions: sudo chmod 0440 /etc/sudoers.d/clientst0r-fail2ban
# Allow $USER user to run fail2ban-client without password
$USER ALL=(ALL) NOPASSWD: /usr/bin/fail2ban-client
FBSUDOEOF
print_status "Sudoers files generated with correct paths for user: $USER"
print_info "Files created:"
print_info " • $INSTALL_DIR/deploy/clientst0r-install-sudoers"
print_info " • $INSTALL_DIR/deploy/clientst0r-fail2ban-sudoers"
# Step 6: Database setup
echo ""
print_info "Step 6/11: Database setup..."
print_info "We'll now create the database and user."
# Check if MariaDB/MySQL is running
if ! sudo systemctl is-active --quiet mariadb && ! sudo systemctl is-active --quiet mysql; then
print_warning "MariaDB/MySQL is not running. Attempting to start..."
if sudo systemctl start mariadb 2>/dev/null || sudo systemctl start mysql 2>/dev/null; then
print_status "Database server started"
else
print_error "Could not start database server. Please install MariaDB:"
echo " sudo apt-get install mariadb-server"
echo " sudo systemctl start mariadb"
echo " sudo mysql_secure_installation"
exit 1
fi
fi
print_info "Creating database and user with secure random password..."
print_warning "You may be prompted for the MySQL root password."
echo ""
# Create database and user with the generated password
sudo mysql << EOSQL
CREATE DATABASE IF NOT EXISTS clientst0r CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS 'clientst0r'@'localhost' IDENTIFIED BY '${DB_PASSWORD}';
GRANT ALL PRIVILEGES ON clientst0r.* TO 'clientst0r'@'localhost';
FLUSH PRIVILEGES;
EOSQL
if [ $? -eq 0 ]; then
print_status "Database created successfully"
print_info " Database: clientst0r"
print_info " User: clientst0r"
print_info " Password: (stored in .env file)"
else
print_error "Database creation failed. You may need to create it manually."
echo ""
echo "Run these commands in MySQL as root (use the password from .env file):"
echo " CREATE DATABASE clientst0r CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
echo " CREATE USER 'clientst0r'@'localhost' IDENTIFIED BY '<password from .env>';"
echo " GRANT ALL PRIVILEGES ON clientst0r.* TO 'clientst0r'@'localhost';"
echo " FLUSH PRIVILEGES;"
echo ""
read -p "Press Enter once you've created the database manually..."
fi
# Step 7: Create log directory
print_info "Step 7/10: Creating log directory..."
sudo mkdir -p /var/log/itdocs
sudo chown $USER:$USER /var/log/itdocs
sudo chmod 755 /var/log/itdocs
print_status "Log directory created"
# Step 8: Run migrations
print_info "Step 8/10: Running database migrations..."
python3 manage.py migrate
print_status "Database migrations completed"
# Step 8.5: Initial organization setup
echo ""
print_info "Step 8.5/10: Initial organization setup..."
print_info "Running setup.py to create your organization..."
python3 setup.py
print_status "Organization setup completed"
# Step 9: Create superuser
echo ""
print_info "Step 9/10: Creating superuser account..."
# Create superuser non-interactively
# Default credentials: admin / ChangeMe123!
export DJANGO_SUPERUSER_PASSWORD='ChangeMe123!'
python3 manage.py createsuperuser \
--username admin \
--email admin@clientst0r.local \
--noinput 2>/dev/null || true
if python3 manage.py shell -c "from django.contrib.auth import get_user_model; User = get_user_model(); exit(0 if User.objects.filter(username='admin').exists() else 1)" 2>/dev/null; then
print_status "Superuser created successfully"
print_info " Username: admin"
print_info " Password: ChangeMe123!"
print_warning " ⚠️ CHANGE THIS PASSWORD after first login!"
else
print_warning "Superuser creation skipped (may already exist or failed)"
print_info " You can create one manually: python3 manage.py createsuperuser"
fi
unset DJANGO_SUPERUSER_PASSWORD
# Step 10: Seed default templates (on fresh install only)
print_info "Step 10/12: Seeding default templates..."
if python3 manage.py shell -c "from docs.models import Document, Diagram; exit(0 if Document.objects.filter(is_template=True).count() == 0 and Diagram.objects.filter(is_template=True).count() == 0 else 1)" 2>/dev/null; then
# Fresh install - seed templates
print_info " Populating document categories..."
python3 manage.py populate_doc_categories > /dev/null 2>&1 || true
print_info " Seeding document templates..."
python3 manage.py seed_templates > /dev/null 2>&1 || true
print_info " Seeding diagram templates..."
python3 manage.py seed_diagram_templates > /dev/null 2>&1 || true
print_info " Populating MSP knowledge base..."
python3 manage.py populate_msp_kb > /dev/null 2>&1 || true
print_status "Default templates seeded successfully"
else
print_info " Templates already exist, skipping seed"
fi
# Step 11: Collect static files
print_info "Step 11/12: Collecting static files..."
python3 manage.py collectstatic --noinput > /dev/null 2>&1
print_status "Static files collected"
# Step 12: Start the server automatically
echo ""
print_info "Step 12/12: Starting production server..."
# Stop any existing service and kill processes
sudo systemctl stop clientst0r-gunicorn.service 2>/dev/null || true
sudo pkill -9 gunicorn 2>/dev/null || true
sleep 2
# Create systemd service file
sudo tee /etc/systemd/system/clientst0r-gunicorn.service > /dev/null << 'SVCEOF'
[Unit]
Description=Client St0r Gunicorn
After=network.target mariadb.service
Wants=mariadb.service
[Service]
Type=simple
User=USER_PLACEHOLDER
Group=USER_PLACEHOLDER
WorkingDirectory=WORKDIR_PLACEHOLDER
Environment="PATH=WORKDIR_PLACEHOLDER/venv/bin"
ExecStart=WORKDIR_PLACEHOLDER/venv/bin/gunicorn \
--workers 4 \
--bind 0.0.0.0:8000 \
--timeout 120 \
--access-logfile /var/log/itdocs/gunicorn-access.log \
--error-logfile /var/log/itdocs/gunicorn-error.log \
--log-level info \
config.wsgi:application
Restart=always
[Install]
WantedBy=multi-user.target
SVCEOF
# Replace placeholders with actual values
sudo sed -i "s|USER_PLACEHOLDER|$USER|g" /etc/systemd/system/clientst0r-gunicorn.service
sudo sed -i "s|WORKDIR_PLACEHOLDER|$INSTALL_DIR|g" /etc/systemd/system/clientst0r-gunicorn.service
# Reload systemd and start service
sudo systemctl daemon-reload
sudo systemctl enable clientst0r-gunicorn.service 2>&1 | grep -v "Created symlink" || true
sudo systemctl start clientst0r-gunicorn.service
# Wait for service to start (gunicorn takes a moment to initialize workers)
sleep 4
# Check if service started successfully
if sudo systemctl is-active --quiet clientst0r-gunicorn.service; then
print_status "Production server started successfully!"
else
print_error "Failed to start production server. Checking logs..."
sudo journalctl -u clientst0r-gunicorn.service -n 30 --no-pager
echo ""
print_info "Trying to diagnose the issue..."
sudo ss -tlnp | grep :8000 || echo "Port 8000 is not in use"
exit 1
fi
# Get server IP address
SERVER_IP=$(hostname -I | awk '{print $1}')
# Print summary
echo ""
echo ""
echo -e "${GREEN}╔═══════════════════════════════════════════════════════════╗${NC}"
echo -e "${GREEN}║ ║${NC}"
echo -e "${GREEN}║ Client St0r Installation Complete! 🎉 ║${NC}"
echo -e "${GREEN}║ Server is RUNNING! ║${NC}"
echo -e "${GREEN}║ ║${NC}"
echo -e "${GREEN}╚═══════════════════════════════════════════════════════════╝${NC}"
echo ""
print_info "🌐 Access your Client St0r installation at:"
echo ""
echo -e " ${GREEN}➜ http://${SERVER_IP}:8000${NC}"
echo -e " ${GREEN}➜ http://localhost:8000${NC} (if accessing from this server)"
echo ""
print_info "🔐 Login Credentials:"
echo " • Username: (the username you created)"
echo " • Password: (the password you entered)"
echo ""
print_info "📊 What's Running:"
echo " ✅ MariaDB Database: clientst0r"
echo " ✅ Gunicorn Server: 4 workers on port 8000"
echo " ✅ Auto-start on boot: Enabled"
echo " ✅ Auto-restart on failure: Enabled"
echo ""
print_info "🔧 Server Management:"
echo " • Check status: sudo systemctl status clientst0r-gunicorn.service"
echo " • Restart: sudo systemctl restart clientst0r-gunicorn.service"
echo " • Stop: sudo systemctl stop clientst0r-gunicorn.service"
echo " • View logs: sudo journalctl -u clientst0r-gunicorn.service -f"
echo ""
print_warning "⚠️ IMPORTANT SECURITY STEPS:"
echo " 1. Backup your .env file (contains secure database password)"
echo " 2. Set DEBUG=False in .env for production"
echo " 3. Update ALLOWED_HOSTS in .env with your domain/IP"
echo " 4. Enable 2FA after first login (Profile → Two-Factor Authentication)"
echo " 5. Create an Organization (Dashboard → Organizations)"
echo ""
print_info "🔐 Security Notes:"
echo " ✅ Random secure database password generated (32 characters)"
echo " ✅ Random Django SECRET_KEY generated (50+ characters)"
echo " ✅ Random encryption keys generated"
echo " 📄 All credentials stored in: $INSTALL_DIR/.env"
echo ""
print_info "📚 Documentation: https://github.com/agit8or1/clientst0r"
echo ""
print_status "Installation files:"
echo " • Install directory: $INSTALL_DIR"
echo " • Config file: $INSTALL_DIR/.env"
echo " • Virtual env: $INSTALL_DIR/venv"
echo " • Logs: /var/log/itdocs/"
echo ""
# Setup mobile app building
print_info "📱 Setting up mobile app building..."
if bash "$INSTALL_DIR/deploy/setup_mobile_build.sh"; then
print_status "Mobile app building configured (automatic Node.js/npm installation)"
else
print_warning "Mobile app setup skipped (optional feature)"
fi
echo ""