# IRT Bank Soal - AaPanel Deployment Guide **Document Version:** 1.1 **Date:** March 21, 2026 **Project:** IRT-Powered Adaptive Question Bank System v1.2.0 **Updated:** Clarified PostgreSQL setup using Databases > PgSQL menu --- ## Table of Contents 1. [Prerequisites](#1-prerequisites) 2. [AaPanel Installation](#2-aapanel-installation) 3. [Install Required Software via AaPanel](#3-install-required-software-via-aapanel) 4. [PostgreSQL Setup](#4-postgresql-setup) 5. [Python Manager Setup](#5-python-manager-setup) 6. [Project Deployment](#6-project-deployment) 7. [Environment Configuration](#7-environment-configuration) 8. [Database Migration](#8-database-migration) 9. [Running the Application](#9-running-the-application) 10. [Nginx Reverse Proxy Configuration](#10-nginx-reverse-proxy-configuration) 11. [SSL Configuration](#11-ssl-configuration) 12. [Post-Deployment Verification](#12-post-deployment-verification) 13. [Troubleshooting](#13-troubleshooting) --- ## 1. Prerequisites ### Server Requirements | Requirement | Minimum | Recommended | |-------------|---------|-------------| | OS | Ubuntu 20.04 / CentOS 7+ | Ubuntu 22.04 LTS | | RAM | 2 GB | 4 GB+ | | Storage | 20 GB | 50 GB+ | | CPU | 1 vCPU | 2+ vCPU | ### Domain Requirements - A domain name pointed to your server IP - Subdomain recommended (e.g., `api.yourdomain.com`) --- ## 2. AaPanel Installation ### Step 2.1: Install AaPanel **For Ubuntu/Debian:** ```bash # Login to your server via SSH ssh root@your-server-ip # Install AaPanel wget -O install.sh http://www.aapanel.com/script/install-ubuntu_6.0_en.sh && bash install.sh ``` **For CentOS:** ```bash # Install AaPanel yum install -y wget && wget -O install.sh http://www.aapanel.com/script/install_6.0_en.sh && sh install.sh ``` ### Step 2.2: Access AaPanel 1. After installation completes, note the panel URL and credentials 2. Access AaPanel via browser: `http://your-server-ip:8888` 3. Login with provided credentials 4. **Important:** Change default port and password after first login --- ## 3. Install Required Software via AaPanel ### Step 3.1: Install Nginx 1. In AaPanel, go to **App Store** 2. Find **Nginx** and click **Install** 3. Select version (recommended: 1.24+) 4. Click **Submit** and wait for installation ### Step 3.2: Install Python Manager 1. Go to **App Store** 2. Search for **Python Manager** (or **PM2 Manager**) 3. Click **Install** ### Step 3.3: Install Redis (Optional, for Celery) 1. Go to **App Store** 2. Find **Redis** and click **Install** 3. Click **Submit** --- ## 4. PostgreSQL Setup > **IMPORTANT:** Use **Databases > PgSQL** menu from AaPanel sidebar. > > This menu supports both: > - **Local server** - PostgreSQL installed on your AaPanel server > - **Remote server** - External PostgreSQL (Supabase, Neon, AWS RDS, etc.) ### Step 4.1: Choose Your Database Type You have two options: | Option | Description | Best For | |--------|-------------|----------| | **Remote Database** | External PostgreSQL service (Supabase, Neon, etc.) | Easy setup, managed, free tier available | | **Local Database** | PostgreSQL on your AaPanel server | Full control, no external dependency | --- ### Option A: Remote PostgreSQL Database (RECOMMENDED) Use an external PostgreSQL service: - **Supabase** - https://supabase.com (free tier: 500MB) - **Neon** - https://neon.tech (free tier: 3GB) - **AWS RDS** - https://aws.amazon.com/rds/postgresql/ - **DigitalOcean** - https://www.digitalocean.com/products/managed-databases-postgresql - **Railway** - https://railway.app #### Step 4.A.1: Create Database on Provider 1. Sign up on your chosen provider 2. Create a new PostgreSQL project/database 3. Note down the connection details from dashboard: - **Host** (e.g., `db.xxxxx.supabase.co` or `ep-xxx.us-east-2.aws.neon.tech`) - **Port** (usually `5432`, Supabase uses `6543` for pooler) - **Database name** (e.g., `postgres` or `neondb`) - **Username** (e.g., `postgres.xxxxx`) - **Password** #### Step 4.A.2: Add Remote Server to AaPanel PgSQL 1. In AaPanel, go to **Databases** > **PgSQL** 2. Click **Remote DB** button 3. Fill in the form: - **Server Name:** `my-remote-db` (any name you like) - **Server Address:** `db.xxxxx.supabase.co` (your host) - **Port:** `5432` or `6543` (check your provider) - **Root User:** `postgres` or your username - **Root Password:** your password 4. Click **Submit** #### Step 4.A.3: Sync Databases from Remote Server 1. After adding remote server, click **Get DB from server** 2. Select your remote server from dropdown 3. Click **Submit** 4. Your remote databases will appear in the list #### Step 4.A.4: Note Your Connection String Your connection string format: ``` postgresql+asyncpg://username:password@host:port/database_name ``` **Example (Supabase):** ``` postgresql+asyncpg://postgres.xxxxx:YourPassword@aws-0-ap-southeast-1.pooler.supabase.com:6543/postgres ``` **Example (Neon):** ``` postgresql+asyncpg://neondb_owner:YourPassword@ep-xxxx.us-east-2.aws.neon.tech/neondb?sslmode=require ``` --- ### Option B: Local PostgreSQL Database Install PostgreSQL directly on your AaPanel server. #### Step 4.B.1: Install PostgreSQL via Terminal ```bash # SSH into your server ssh root@your-server-ip # Ubuntu/Debian apt update apt install -y postgresql postgresql-contrib # Start and enable PostgreSQL systemctl start postgresql systemctl enable postgresql # Check status systemctl status postgresql ``` #### Step 4.B.2: Create Database and User via Terminal ```bash # Switch to postgres user su - postgres # Enter PostgreSQL CLI psql # Run SQL commands: CREATE DATABASE irt_bank_soal; CREATE USER irt_user WITH ENCRYPTED PASSWORD 'your_secure_password_here'; GRANT ALL PRIVILEGES ON DATABASE irt_bank_soal TO irt_user; # Connect to database and grant schema \c irt_bank_soal GRANT ALL ON SCHEMA public TO irt_user; # Exit \q exit ``` #### Step 4.B.3: Add Local Server to AaPanel PgSQL 1. In AaPanel, go to **Databases** > **PgSQL** 2. Click **Root Password** to view/change postgres password 3. If your local PostgreSQL is not showing, click **Get DB from server** 4. Select **Local server** 5. Click **Submit** #### Step 4.B.4: Create Additional Database via AaPanel (Optional) 1. In **Databases** > **PgSQL** 2. Click **Add DB** 3. Fill in: - **Database name:** `irt_bank_soal` - **Username:** `irt_user` (or same as DB name) - **Password:** (click generate or enter custom) - **Add to:** `Local server` 4. Click **Submit** #### Step 4.B.5: Note Your Connection String ``` postgresql+asyncpg://irt_user:your_password@127.0.0.1:5432/irt_bank_soal ``` --- ## 4.1 Test Database Connection Before proceeding, verify your database connection works. ### For Remote Database: ```bash # Install psql client if needed apt install -y postgresql-client # Test connection (replace with your details) psql "postgresql://username:password@host:port/database_name" -c "SELECT version();" ``` ### For Local Database: ```bash # Test connection psql -U irt_user -d irt_bank_soal -h 127.0.0.1 -c "SELECT version();" # If prompted for password, enter it ``` --- ## 4.2 Connection String Quick Reference | Database Type | Connection String Format | |---------------|-------------------------| | **Remote (Supabase)** | `postgresql+asyncpg://postgres.xxxx:password@aws-0-region.pooler.supabase.com:6543/postgres` | | **Remote (Neon)** | `postgresql+asyncpg://user:password@ep-xxxx.region.aws.neon.tech/neondb?sslmode=require` | | **Local** | `postgresql+asyncpg://irt_user:password@127.0.0.1:5432/irt_bank_soal` | > **Note:** We use `postgresql+asyncpg://` because our app uses async SQLAlchemy with `asyncpg` driver. --- ## 5. Python Manager Setup ### Step 5.1: Open Python Manager 1. In AaPanel, go to **App Store** 2. Find **Python Manager** and click **Settings** ### Step 5.2: Install Python Version 1. Click **Version Management** 2. Select **Python 3.11** (or latest stable) 3. Click **Install** 4. Wait for installation to complete --- ## 6. Project Deployment ### Step 6.1: Create Project Directory ```bash # Create project directory mkdir -p /www/wwwroot/irt-bank-soal # Navigate to directory cd /www/wwwroot/irt-bank-soal ``` ### Step 6.2: Upload Project Files **Option A: Upload via File Manager** 1. In AaPanel, go to **Files** 2. Navigate to `/www/wwwroot/irt-bank-soal` 3. Upload your project ZIP file 4. Extract the archive **Option B: Clone from Git (if applicable)** ```bash cd /www/wwwroot/irt-bank-soal # If using Git git clone https://github.com/your-repo/irt-bank-soal.git . # Or copy from local # scp -r /Users/dwindown/Applications/tryout-system/* root@your-server-ip:/www/wwwroot/irt-bank-soal/ ``` ### Step 6.3: Verify Project Structure ```bash # Expected structure: ls -la /www/wwwroot/irt-bank-soal/ # app/ # app/models/ # app/routers/ # app/services/ # app/core/ # tests/ # requirements.txt # .env.example # alembic/ ``` --- ## 7. Environment Configuration ### Step 7.1: Create Virtual Environment via Python Manager 1. In AaPanel **Python Manager**, click **Add Project** 2. Configure: - **Project Name:** `irt-bank-soal` - **Project Path:** `/www/wwwroot/irt-bank-soal` - **Python Version:** `Python 3.11` - **Framework:** `FastAPI` - **Startup Method:** `uvicorn` 3. Click **Submit** ### Step 7.2: Create Environment File ```bash # Copy example file cp /www/wwwroot/irt-bank-soal/.env.example /www/wwwroot/irt-bank-soal/.env # Edit .env file nano /www/wwwroot/irt-bank-soal/.env ``` ### Step 7.3: Configure .env File ```env # Database Configuration # For Remote Database (Supabase example): # DATABASE_URL=postgresql+asyncpg://postgres.xxxx:password@aws-0-ap-southeast-1.pooler.supabase.com:6543/postgres # For Remote Database (Neon example): # DATABASE_URL=postgresql+asyncpg://neondb_owner:password@ep-xxxx.us-east-2.aws.neon.tech/neondb?sslmode=require # For Local Database: DATABASE_URL=postgresql+asyncpg://irt_user:your_secure_password_here@127.0.0.1:5432/irt_bank_soal # Security SECRET_KEY=your-production-secret-key-min-32-characters-random-string # Environment ENVIRONMENT=production DEBUG=false # API Configuration API_V1_STR=/api/v1 PROJECT_NAME=IRT Bank Soal PROJECT_VERSION=1.2.0 # CORS - Add your WordPress domains ALLOWED_ORIGINS=https://yourdomain.com,https://www.yourdomain.com # OpenRouter API (for AI Generation) OPENROUTER_API_KEY=your-openrouter-api-key-here OPENROUTER_API_URL=https://openrouter.ai/api/v1 OPENROUTER_MODEL_QWEN=qwen/qwen-2.5-coder-32b-instruct OPENROUTER_MODEL_LLAMA=meta-llama/llama-3.3-70b-instruct OPENROUTER_TIMEOUT=60 # WordPress Integration WORDPRESS_API_URL=https://yourdomain.com/wp-json WORDPRESS_AUTH_TOKEN=your-wordpress-jwt-token # Redis (for Celery task queue) REDIS_URL=redis://127.0.0.1:6379/0 # Admin Panel ADMIN_USER=admin ADMIN_PASSWORD=your-secure-admin-password # Normalization Defaults DEFAULT_RATAAN=500 DEFAULT_SB=100 MIN_SAMPLE_FOR_DYNAMIC=100 ``` ### Step 7.4: Generate Secret Key ```bash # Generate a secure secret key python3 -c "import secrets; print(secrets.token_urlsafe(32))" # Copy the output and paste into SECRET_KEY in .env ``` --- ## 8. Database Migration ### Step 8.1: Activate Virtual Environment ```bash # Via Python Manager, the venv is usually at: source /www/wwwroot/irt-bank-soal/venv/bin/activate # Or check Python Manager for exact venv path ``` ### Step 8.2: Install Dependencies ```bash # Ensure you're in project directory cd /www/wwwroot/irt-bank-soal # Install dependencies pip install -r requirements.txt # Verify installation pip list | grep -E "fastapi|sqlalchemy|numpy|scipy|httpx|openpyxl" ``` ### Step 8.3: Initialize Alembic (First Time Setup) ```bash # Initialize Alembic if not already done alembic init alembic # Generate initial migration alembic revision --autogenerate -m "Initial migration" # Apply migration alembic upgrade head ``` ### Step 8.4: Verify Database Tables ```bash # Check tables were created psql -U irt_user -d irt_bank_soal -h 127.0.0.1 -c "\dt" # Expected output: websites, users, tryouts, items, sessions, user_answers, tryout_stats ``` --- ## 9. Running the Application ### Step 9.1: Configure Python Project in AaPanel 1. In **Python Manager**, find your project `irt-bank-soal` 2. Click **Settings** 3. Configure startup: - **Startup File:** `app/main.py` - **Startup Method:** `uvicorn` - **Port:** `8000` - **Modules:** `uvicorn[standard]` ### Step 9.2: Set Startup Command In Python Manager settings, set the startup command: ```bash # Startup command uvicorn app.main:app --host 127.0.0.1 --port 8000 --workers 4 # Or for development: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload ``` ### Step 9.3: Start the Application 1. In Python Manager, click **Start** on your project 2. Check logs for any errors 3. Verify the application is running: ```bash # Test health endpoint curl http://127.0.0.1:8000/ # Expected response: # {"status": "healthy", "project_name": "IRT Bank Soal", "version": "1.2.0"} ``` ### Step 9.4: Configure Auto-Start on Boot 1. In Python Manager, enable **Auto-start on boot** 2. Or manually via terminal: ```bash # Using systemd (create service file) nano /etc/systemd/system/irt-bank-soal.service ``` ```ini [Unit] Description=IRT Bank Soal FastAPI Application After=network.target # Uncomment below if using LOCAL PostgreSQL: # After=network.target postgresql.service [Service] Type=simple User=www Group=www WorkingDirectory=/www/wwwroot/irt-bank-soal Environment="PATH=/www/wwwroot/irt-bank-soal/venv/bin" ExecStart=/www/wwwroot/irt-bank-soal/venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000 --workers 4 Restart=always RestartSec=5 [Install] WantedBy=multi-user.target ``` ```bash # Enable and start service systemctl daemon-reload systemctl enable irt-bank-soal systemctl start irt-bank-soal systemctl status irt-bank-soal ``` --- ## 10. Nginx Reverse Proxy Configuration ### Step 10.1: Create Website in AaPanel 1. In AaPanel, go to **Website** 2. Click **Add Site** 3. Configure: - **Domain:** `api.yourdomain.com` (or your subdomain) - **PHP Version:** Pure Static (not needed) - **Database:** None (already created) 4. Click **Submit** ### Step 10.2: Configure Reverse Proxy 1. Click **Settings** on the newly created website 2. Go to **Reverse Proxy** 3. Click **Add Reverse Proxy** 4. Configure: - **Proxy Name:** `irt-api` - **Target URL:** `http://127.0.0.1:8000` 5. Click **Submit** ### Step 10.3: Manual Nginx Configuration (Alternative) ```bash # Edit Nginx config nano /www/server/panel/vhost/nginx/api.yourdomain.com.conf ``` ```nginx server { listen 80; server_name api.yourdomain.com; # Access and error logs access_log /www/wwwlogs/api.yourdomain.com.log; error_log /www/wwwlogs/api.yourdomain.com.error.log; # Client body size (for Excel uploads) client_max_body_size 50M; # Proxy to FastAPI location / { proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } # Static files (if any) location /static/ { alias /www/wwwroot/irt-bank-soal/static/; expires 30d; } } ``` ### Step 10.4: Test and Reload Nginx ```bash # Test Nginx configuration nginx -t # Reload Nginx nginx -s reload # Or via AaPanel: Website > Settings > Config > Save ``` --- ## 11. SSL Configuration ### Step 11.1: Install SSL Certificate 1. In AaPanel, go to **Website** 2. Click **Settings** on your site 3. Go to **SSL** 4. Choose method: - **Let's Encrypt:** Free, auto-renewal - **Own Certificate:** Upload your own - **Buy:** Purchase through AaPanel ### Step 11.2: Configure Let's Encrypt 1. Click **Let's Encrypt** 2. Enter your email 3. Select domain `api.yourdomain.com` 4. Click **Apply** 5. Enable **Force HTTPS** ### Step 11.3: Update .env for HTTPS ```bash # Edit .env nano /www/wwwroot/irt-bank-soal/.env # Update CORS to use HTTPS ALLOWED_ORIGINS=https://yourdomain.com,https://www.yourdomain.com ``` --- ## 12. Post-Deployment Verification ### Step 12.1: Test API Endpoints ```bash # Test health endpoint curl https://api.yourdomain.com/ # Test detailed health curl https://api.yourdomain.com/health # Test API documentation # Open in browser: https://api.yourdomain.com/docs ``` ### Step 12.2: Test Database Connection ```bash # Via API curl https://api.yourdomain.com/health # Expected response includes database status: # {"status": "healthy", "database": "connected", "api_version": "v1"} ``` ### Step 12.3: Test Admin Panel ```bash # Access admin panel # Open in browser: https://api.yourdomain.com/admin # Login with credentials from .env ``` ### Step 12.4: Load Test Data (Optional) ```bash # SSH into server ssh root@your-server-ip # Navigate to project cd /www/wwwroot/irt-bank-soal # Activate venv source venv/bin/activate # Run test data script python3 -c " import asyncio from app.database import init_db asyncio.run(init_db()) print('Database initialized successfully') " ``` --- ## 13. Troubleshooting ### Issue: Python Manager Not Starting Application **Solution:** ```bash # Check logs tail -f /www/wwwroot/irt-bank-soal/logs/error.log # Check if port is in use lsof -i :8000 # Manually test startup cd /www/wwwroot/irt-bank-soal source venv/bin/activate uvicorn app.main:app --host 127.0.0.1 --port 8000 ``` ### Issue: Database Connection Failed **For Remote Database:** ```bash # Test connection from server apt install -y postgresql-client psql "postgresql://username:password@remote-host:port/database" -c "SELECT 1;" # Check if firewall allows outbound connection # Most remote DBs use port 5432 or 6543 # Verify DATABASE_URL in .env cat /www/wwwroot/irt-bank-soal/.env | grep DATABASE_URL # Common issues: # - Wrong port (Supabase pooler uses 6543, direct uses 5432) # - Missing sslmode=require (Neon requires this) # - IP not whitelisted (check provider dashboard) ``` **For Local Database:** ```bash # Check PostgreSQL status systemctl status postgresql # Test connection manually psql -U irt_user -d irt_bank_soal -h 127.0.0.1 -W # Check pg_hba.conf allows connections cat /etc/postgresql/*/main/pg_hba.conf | grep -v "^#" | grep -v "^$" # Verify DATABASE_URL in .env cat /www/wwwroot/irt-bank-soal/.env | grep DATABASE_URL ``` ### Issue: 502 Bad Gateway **Solution:** ```bash # Check if FastAPI is running ps aux | grep uvicorn # Check Nginx error logs tail -f /www/wwwlogs/api.yourdomain.com.error.log # Verify proxy configuration cat /www/server/panel/vhost/nginx/api.yourdomain.com.conf | grep proxy_pass ``` ### Issue: CORS Errors **Solution:** ```bash # Check ALLOWED_ORIGINS in .env cat /www/wwwroot/irt-bank-soal/.env | grep ALLOWED_ORIGINS # Ensure WordPress domain is included # Example: ALLOWED_ORIGINS=https://site1.com,https://site2.com # Restart application after changes # Via Python Manager: Stop > Start ``` ### Issue: SSL Certificate Not Working **Solution:** ```bash # Check certificate openssl s_client -connect api.yourdomain.com:443 # Force HTTPS in Nginx config # Add to server block: # return 301 https://$host$request_uri; # Reload Nginx nginx -s reload ``` ### Issue: Large File Upload Failed **Solution:** ```bash # Increase Nginx client body size nano /www/server/panel/vhost/nginx/api.yourdomain.com.conf # Add/modify: # client_max_body_size 100M; # Also check PHP settings if using PHP # In AaPanel: PHP > Settings > Upload Max Filesize ``` --- ## Quick Reference Commands ```bash # Application Management systemctl start irt-bank-soal systemctl stop irt-bank-soal systemctl restart irt-bank-soal systemctl status irt-bank-soal # Local Database Management (if using local PostgreSQL) systemctl start postgresql systemctl stop postgresql systemctl restart postgresql systemctl status postgresql # Nginx Management nginx -t # Test config nginx -s reload # Reload config systemctl restart nginx # Restart Nginx # View Logs tail -f /www/wwwlogs/api.yourdomain.com.log tail -f /www/wwwlogs/api.yourdomain.com.error.log # Application Logs (if configured) tail -f /www/wwwroot/irt-bank-soal/logs/app.log # Test Database Connection # Local: psql -U irt_user -d irt_bank_soal -h 127.0.0.1 -c "SELECT version();" # Remote: psql "postgresql://user:pass@host:port/db" -c "SELECT version();" ``` --- ## Security Checklist - [ ] Changed AaPanel default port and password - [ ] Database user has strong password - [ ] SECRET_KEY is unique and 32+ characters - [ ] SSL certificate installed and forced HTTPS - [ ] CORS restricted to production domains only - [ ] Firewall configured (only 80, 443, 22, 8888 open) - [ ] Admin password is strong - [ ] For local DB: PostgreSQL not exposed to internet - [ ] For remote DB: IP whitelist configured (if supported) - [ ] Regular backups configured --- ## Backup Configuration ### Database Backup **For Local Database:** ```bash # Create backup directory mkdir -p /www/backup # Manual backup pg_dump -U irt_user -h 127.0.0.1 irt_bank_soal > /www/backup/irt_bank_soal_$(date +%Y%m%d).sql # Automated backup (cron) crontab -e # Add: 0 2 * * * pg_dump -U irt_user -h 127.0.0.1 irt_bank_soal > /www/backup/irt_bank_soal_$(date +\%Y\%m\%d).sql ``` **For Remote Database:** Most managed PostgreSQL providers have built-in backup features: - **Supabase:** Dashboard > Database > Backups (daily automatic) - **Neon:** Automatic point-in-time recovery - **AWS RDS:** Automated backups with retention period You can also backup manually: ```bash # Manual backup from remote (requires postgresql-client) pg_dump "postgresql://username:password@host:port/database" > /www/backup/irt_bank_soal_$(date +%Y%m%d).sql # Or with SSL for providers like Neon pg_dump "postgresql://username:password@host:port/database?sslmode=require" > /www/backup/irt_bank_soal_$(date +%Y%m%d).sql ``` ### Project Backup ```bash # Backup project files tar -czvf /www/backup/irt_project_$(date +%Y%m%d).tar.gz /www/wwwroot/irt-bank-soal # Exclude venv to save space tar -czvf /www/backup/irt_project_$(date +%Y%m%d).tar.gz --exclude='venv' /www/wwwroot/irt-bank-soal ``` --- **Document End** **Status:** Ready for Deployment **Support:** Refer to TEST.md for testing procedures and PRD.md for requirements.