feat: add PostgreSQL support and database configuration options

- Updated Docker configuration to support PostgreSQL as an alternative to SQLite.
- Enhanced DockerEntrypoint.sh to create a database environment file and test PostgreSQL connection.
- Introduced database setup functions in install.sh for PostgreSQL installation and configuration.
- Added database management options in x-ui.sh, including backup and switching between SQLite and PostgreSQL.
- Implemented database configuration retrieval in the web service and controller layers.
- Updated frontend settings to include database configuration options.
- Added translations for new database settings in multiple languages.
This commit is contained in:
izzzzzi 2025-05-23 02:00:06 +05:00
parent 5dae785786
commit 2fbd1d860d
25 changed files with 1969 additions and 108 deletions

View file

@ -1,7 +1,61 @@
#!/bin/sh
# Function to create database environment file
create_db_env() {
mkdir -p /etc/x-ui
cat > /etc/x-ui/db.env << EOF
DB_TYPE=${DB_TYPE:-sqlite}
DB_HOST=${DB_HOST:-localhost}
DB_PORT=${DB_PORT:-5432}
DB_NAME=${DB_NAME:-x_ui}
DB_USER=${DB_USER:-x_ui}
DB_PASSWORD=${DB_PASSWORD}
DB_SSLMODE=${DB_SSLMODE:-disable}
DB_TIMEZONE=${DB_TIMEZONE:-UTC}
EOF
chmod 600 /etc/x-ui/db.env
}
# Function to wait for PostgreSQL
wait_for_postgres() {
if [ "$DB_TYPE" = "postgres" ]; then
echo "Waiting for PostgreSQL to be ready..."
/app/wait-for-postgres.sh "$DB_HOST" "$DB_PORT" "$DB_USER" "$DB_NAME"
echo "PostgreSQL is ready!"
fi
}
# Function to test database connection
test_db_connection() {
if [ "$DB_TYPE" = "postgres" ]; then
echo "Testing PostgreSQL connection..."
if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT 1;" > /dev/null 2>&1; then
echo "PostgreSQL connection successful!"
else
echo "ERROR: Cannot connect to PostgreSQL database!"
echo "Please check your database configuration:"
echo " DB_HOST: $DB_HOST"
echo " DB_PORT: $DB_PORT"
echo " DB_NAME: $DB_NAME"
echo " DB_USER: $DB_USER"
exit 1
fi
else
echo "Using SQLite database"
fi
}
# Create database environment file
create_db_env
# Wait for PostgreSQL if needed
wait_for_postgres
# Test database connection
test_db_connection
# Start fail2ban
[ $XUI_ENABLE_FAIL2BAN == "true" ] && fail2ban-client -x start
[ "$XUI_ENABLE_FAIL2BAN" = "true" ] && fail2ban-client -x start
# Run x-ui
exec /app/x-ui

View file

@ -29,12 +29,14 @@ RUN apk add --no-cache --update \
ca-certificates \
tzdata \
fail2ban \
bash
bash \
postgresql-client \
curl
COPY --from=builder /app/build/ /app/
COPY --from=builder /app/DockerEntrypoint.sh /app/
COPY --from=builder /app/x-ui.sh /usr/bin/x-ui
COPY wait-for-postgres.sh /app/
# Configure fail2ban
RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \
@ -46,9 +48,20 @@ RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \
RUN chmod +x \
/app/DockerEntrypoint.sh \
/app/x-ui \
/app/wait-for-postgres.sh \
/usr/bin/x-ui
# Environment variables for database configuration
ENV XUI_ENABLE_FAIL2BAN="true"
ENV DB_TYPE="sqlite"
ENV DB_HOST="localhost"
ENV DB_PORT="5432"
ENV DB_NAME="x_ui"
ENV DB_USER="x_ui"
ENV DB_PASSWORD=""
ENV DB_SSLMODE="disable"
ENV DB_TIMEZONE="UTC"
VOLUME [ "/etc/x-ui" ]
CMD [ "./x-ui" ]
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]

347
README.Docker.md Normal file
View file

@ -0,0 +1,347 @@
# 🐳 3X-UI Docker Setup with PostgreSQL Support
This guide explains how to run 3X-UI with Docker, supporting both SQLite (default) and PostgreSQL databases.
## 📋 Quick Start
### Option 1: SQLite (Default, Recommended for most users)
```bash
# Clone the repository
git clone https://github.com/MHSanaei/3x-ui.git
cd 3x-ui
# Start with SQLite (default)
docker-compose up -d
```
### Option 2: PostgreSQL (Production setup)
```bash
# Clone the repository
git clone https://github.com/MHSanaei/3x-ui.git
cd 3x-ui
# Copy environment file and configure
cp env.example .env
# Edit .env file with your PostgreSQL settings
# Start with PostgreSQL
docker-compose -f docker-compose.postgresql.yml up -d
```
## 🔧 Configuration
### Environment Variables
Copy `env.example` to `.env` and modify according to your needs:
```bash
cp env.example .env
```
Key variables:
| Variable | Default | Description |
|----------|---------|-------------|
| `DB_TYPE` | `sqlite` | Database type: `sqlite` or `postgres` |
| `DB_HOST` | `localhost` | PostgreSQL host |
| `DB_PORT` | `5432` | PostgreSQL port |
| `DB_NAME` | `x_ui` | Database name |
| `DB_USER` | `x_ui` | Database user |
| `DB_PASSWORD` | - | Database password (required for PostgreSQL) |
| `XUI_PORT` | `2053` | 3X-UI web interface port |
| `XUI_SUB_PORT` | `2096` | Subscription port |
### Database Types
#### SQLite (Default)
- **Pros**: Simple setup, no additional containers, good for small-medium loads
- **Cons**: Limited concurrent connections, single file storage
- **Use case**: Personal use, small teams, development
#### PostgreSQL
- **Pros**: High performance, concurrent connections, ACID compliance, scalability
- **Cons**: More complex setup, additional container
- **Use case**: Production environments, high load, multiple users
## 🚀 Deployment Options
### 1. SQLite Deployment
```bash
# Basic SQLite setup
docker-compose up -d
# Check logs
docker-compose logs -f 3x-ui
```
### 2. PostgreSQL Deployment
```bash
# Set environment variables
export DB_PASSWORD="your_secure_password_here"
# Start PostgreSQL setup
docker-compose -f docker-compose.postgresql.yml up -d
# Check all services
docker-compose -f docker-compose.postgresql.yml ps
```
### 3. PostgreSQL with Admin Interface
```bash
# Start with PgAdmin for database management
docker-compose -f docker-compose.postgresql.yml --profile admin up -d
# Access PgAdmin at http://localhost:5050
# Default login: admin@example.com / admin_password
```
### 4. External PostgreSQL
```bash
# Configure .env for external database
cat > .env << EOF
DB_TYPE=postgres
DB_HOST=your-postgres-server.com
DB_PORT=5432
DB_NAME=x_ui_production
DB_USER=x_ui_user
DB_PASSWORD=your_external_db_password
DB_SSLMODE=require
EOF
# Start only 3X-UI container
docker-compose up -d
```
## 🔍 Monitoring and Maintenance
### Health Checks
All services include health checks:
```bash
# Check service health
docker-compose ps
# View health check logs
docker inspect --format='{{json .State.Health}}' 3x-ui
```
### Logs
```bash
# View 3X-UI logs
docker-compose logs -f 3x-ui
# View PostgreSQL logs
docker-compose -f docker-compose.postgresql.yml logs -f postgres
# View all logs
docker-compose -f docker-compose.postgresql.yml logs -f
```
### Database Management
#### Backup PostgreSQL
```bash
# Create backup
docker-compose -f docker-compose.postgresql.yml exec postgres pg_dump -U x_ui x_ui > backup.sql
# Restore backup
docker-compose -f docker-compose.postgresql.yml exec -T postgres psql -U x_ui x_ui < backup.sql
```
#### Backup SQLite
```bash
# SQLite backup
docker-compose exec 3x-ui cp /etc/x-ui/x-ui.db /etc/x-ui/x-ui.db.backup
docker cp 3x-ui:/etc/x-ui/x-ui.db.backup ./x-ui-backup.db
```
## 🔧 Troubleshooting
### Common Issues
#### PostgreSQL Connection Failed
```bash
# Check PostgreSQL is running
docker-compose -f docker-compose.postgresql.yml ps postgres
# Check PostgreSQL logs
docker-compose -f docker-compose.postgresql.yml logs postgres
# Test connection manually
docker-compose -f docker-compose.postgresql.yml exec postgres psql -U x_ui -d x_ui -c "SELECT 1;"
```
#### 3X-UI Won't Start
```bash
# Check 3X-UI logs
docker-compose logs 3x-ui
# Check database environment
docker-compose exec 3x-ui cat /etc/x-ui/db.env
# Restart services
docker-compose restart
```
#### Port Conflicts
```bash
# Change ports in .env file
echo "XUI_PORT=3053" >> .env
echo "XUI_SUB_PORT=3096" >> .env
# Restart with new ports
docker-compose up -d
```
### Performance Tuning
#### PostgreSQL Optimization
The PostgreSQL container is pre-configured with optimized settings for 3X-UI workload:
- `max_connections=200`
- `shared_buffers=256MB`
- `effective_cache_size=1GB`
- `work_mem=4MB`
For high-load environments, consider:
1. **Increase resources**:
```yaml
deploy:
resources:
limits:
memory: 2G
cpus: '1.0'
```
2. **Use external PostgreSQL** with dedicated hardware
3. **Enable connection pooling** (PgBouncer)
## 🔒 Security Considerations
### Production Checklist
- [ ] Change default passwords in `.env`
- [ ] Use strong, unique passwords
- [ ] Enable SSL for PostgreSQL (`DB_SSLMODE=require`)
- [ ] Restrict network access (firewall rules)
- [ ] Regular backups
- [ ] Monitor logs for suspicious activity
- [ ] Keep containers updated
### Network Security
```yaml
# Example: Restrict PostgreSQL access
services:
postgres:
ports:
- "127.0.0.1:5432:5432" # Only localhost access
```
## 📊 Monitoring
### Prometheus Metrics (Optional)
Add monitoring stack:
```yaml
# Add to docker-compose.postgresql.yml
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
grafana:
image: grafana/grafana
ports:
- "3000:3000"
```
## 🔄 Migration
### SQLite to PostgreSQL
1. **Backup SQLite data**:
```bash
docker-compose exec 3x-ui cp /etc/x-ui/x-ui.db /etc/x-ui/backup.db
```
2. **Export data** (manual process - depends on your data structure)
3. **Switch to PostgreSQL**:
```bash
# Update .env
echo "DB_TYPE=postgres" >> .env
# Start PostgreSQL
docker-compose -f docker-compose.postgresql.yml up -d
```
4. **Import data** (application will create tables automatically)
### PostgreSQL to SQLite
1. **Export PostgreSQL data**
2. **Switch to SQLite** in `.env`
3. **Import data**
## 📞 Support
- **Issues**: [GitHub Issues](https://github.com/MHSanaei/3x-ui/issues)
- **Documentation**: [Main README](README.md)
- **Community**: [Telegram](https://t.me/x_ui_channel)
## 📝 Examples
### Development Setup
```bash
# Quick development setup with SQLite
git clone https://github.com/MHSanaei/3x-ui.git
cd 3x-ui
docker-compose up -d
```
### Production Setup
```bash
# Production setup with PostgreSQL
git clone https://github.com/MHSanaei/3x-ui.git
cd 3x-ui
# Configure environment
cat > .env << EOF
DB_TYPE=postgres
DB_PASSWORD=$(openssl rand -base64 32)
XUI_PORT=2053
XUI_SUB_PORT=2096
HOSTNAME=$(hostname)
EOF
# Deploy
docker-compose -f docker-compose.postgresql.yml up -d
# Verify
docker-compose -f docker-compose.postgresql.yml ps
```
### High Availability Setup
For HA setups, consider:
- External PostgreSQL cluster
- Load balancer for 3X-UI instances
- Shared storage for certificates
- Monitoring and alerting

130
config/database.go Normal file
View file

@ -0,0 +1,130 @@
package config
import (
"fmt"
"os"
"path/filepath"
)
// DatabaseType represents the type of database
type DatabaseType string
const (
DatabaseTypeSQLite DatabaseType = "sqlite"
DatabaseTypePostgreSQL DatabaseType = "postgres"
)
// DatabaseConfig holds database configuration
type DatabaseConfig struct {
Type DatabaseType `json:"type"`
SQLite SQLiteConfig `json:"sqlite"`
Postgres PostgresConfig `json:"postgres"`
}
// SQLiteConfig holds SQLite specific configuration
type SQLiteConfig struct {
Path string `json:"path"`
}
// PostgresConfig holds PostgreSQL specific configuration
type PostgresConfig struct {
Host string `json:"host"`
Port int `json:"port"`
Database string `json:"database"`
Username string `json:"username"`
Password string `json:"password"`
SSLMode string `json:"sslMode"`
TimeZone string `json:"timeZone"`
}
// GetDSN returns the data source name for the database
func (c *DatabaseConfig) GetDSN() string {
switch c.Type {
case DatabaseTypeSQLite:
return c.SQLite.Path
case DatabaseTypePostgreSQL:
return fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=%s",
c.Postgres.Host,
c.Postgres.Username,
c.Postgres.Password,
c.Postgres.Database,
c.Postgres.Port,
c.Postgres.SSLMode,
c.Postgres.TimeZone,
)
default:
return c.SQLite.Path
}
}
// GetDefaultDatabaseConfig returns default database configuration
func GetDefaultDatabaseConfig() *DatabaseConfig {
return &DatabaseConfig{
Type: DatabaseTypeSQLite,
SQLite: SQLiteConfig{
Path: getDefaultSQLitePath(),
},
Postgres: PostgresConfig{
Host: "localhost",
Port: 5432,
Database: "x_ui",
Username: "x_ui",
Password: "",
SSLMode: "disable",
TimeZone: "UTC",
},
}
}
// getDefaultSQLitePath returns the default SQLite database path
func getDefaultSQLitePath() string {
if IsDebug() {
return "db/x-ui.db"
}
return "/etc/x-ui/x-ui.db"
}
// ValidateConfig validates the database configuration
func (c *DatabaseConfig) ValidateConfig() error {
switch c.Type {
case DatabaseTypeSQLite:
if c.SQLite.Path == "" {
return fmt.Errorf("SQLite path cannot be empty")
}
case DatabaseTypePostgreSQL:
if c.Postgres.Host == "" {
return fmt.Errorf("PostgreSQL host cannot be empty")
}
if c.Postgres.Database == "" {
return fmt.Errorf("PostgreSQL database name cannot be empty")
}
if c.Postgres.Username == "" {
return fmt.Errorf("PostgreSQL username cannot be empty")
}
if c.Postgres.Port <= 0 || c.Postgres.Port > 65535 {
return fmt.Errorf("PostgreSQL port must be between 1 and 65535")
}
default:
return fmt.Errorf("unsupported database type: %s", c.Type)
}
return nil
}
// IsPostgreSQL returns true if the database type is PostgreSQL
func (c *DatabaseConfig) IsPostgreSQL() bool {
return c.Type == DatabaseTypePostgreSQL
}
// IsSQLite returns true if the database type is SQLite
func (c *DatabaseConfig) IsSQLite() bool {
return c.Type == DatabaseTypeSQLite
}
// EnsureDirectoryExists ensures the directory for SQLite database exists
func (c *DatabaseConfig) EnsureDirectoryExists() error {
if c.Type == DatabaseTypeSQLite {
dir := filepath.Dir(c.SQLite.Path)
return os.MkdirAll(dir, 0755)
}
return nil
}

View file

@ -1,19 +1,22 @@
package database
import (
"bufio"
"bytes"
"fmt"
"io"
"io/fs"
"log"
"os"
"path"
"slices"
"strconv"
"strings"
"x-ui/config"
"x-ui/database/model"
"x-ui/util/crypto"
"x-ui/xray"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
@ -113,15 +116,109 @@ func isTableEmpty(tableName string) (bool, error) {
return count == 0, err
}
// loadEnvFile loads environment variables from a file
func loadEnvFile(filename string) error {
if _, err := os.Stat(filename); os.IsNotExist(err) {
return nil // File doesn't exist, not an error
}
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 {
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
os.Setenv(key, value)
}
}
return scanner.Err()
}
// getDatabaseConfig retrieves database configuration from settings
func getDatabaseConfig() (*config.DatabaseConfig, error) {
// Load environment variables from file if it exists
if err := loadEnvFile("/etc/x-ui/db.env"); err != nil {
log.Printf("Warning: Could not load database environment file: %v", err)
}
// Try to get configuration from settings
// This is a simplified version - in real implementation you'd get this from SettingService
dbConfig := config.GetDefaultDatabaseConfig()
// Load configuration from environment variables
if dbType := os.Getenv("DB_TYPE"); dbType != "" {
dbConfig.Type = config.DatabaseType(dbType)
}
if dbConfig.Type == config.DatabaseTypePostgreSQL {
if host := os.Getenv("DB_HOST"); host != "" {
dbConfig.Postgres.Host = host
}
if port := os.Getenv("DB_PORT"); port != "" {
if p, err := strconv.Atoi(port); err == nil {
dbConfig.Postgres.Port = p
}
}
if database := os.Getenv("DB_NAME"); database != "" {
dbConfig.Postgres.Database = database
}
if username := os.Getenv("DB_USER"); username != "" {
dbConfig.Postgres.Username = username
}
if password := os.Getenv("DB_PASSWORD"); password != "" {
dbConfig.Postgres.Password = password
}
if sslMode := os.Getenv("DB_SSLMODE"); sslMode != "" {
dbConfig.Postgres.SSLMode = sslMode
}
if timeZone := os.Getenv("DB_TIMEZONE"); timeZone != "" {
dbConfig.Postgres.TimeZone = timeZone
}
}
return dbConfig, nil
}
func InitDB(dbPath string) error {
dir := path.Dir(dbPath)
err := os.MkdirAll(dir, fs.ModePerm)
// Try to get configuration from environment file first
dbConfig, err := getDatabaseConfig()
if err != nil {
return err
}
var gormLogger logger.Interface
// If still using SQLite and dbPath is provided, use it
if dbConfig.Type == config.DatabaseTypeSQLite && dbPath != "" {
dbConfig.SQLite.Path = dbPath
}
return InitDBWithConfig(dbConfig)
}
// InitDBWithConfig initializes database with provided configuration
func InitDBWithConfig(dbConfig *config.DatabaseConfig) error {
// Validate configuration
if err := dbConfig.ValidateConfig(); err != nil {
return err
}
// Ensure directory exists for SQLite
if err := dbConfig.EnsureDirectoryExists(); err != nil {
return err
}
var gormLogger logger.Interface
if config.IsDebug() {
gormLogger = logger.Default
} else {
@ -131,7 +228,18 @@ func InitDB(dbPath string) error {
c := &gorm.Config{
Logger: gormLogger,
}
db, err = gorm.Open(sqlite.Open(dbPath), c)
// Open database connection based on type
var err error
switch dbConfig.Type {
case config.DatabaseTypeSQLite:
db, err = gorm.Open(sqlite.Open(dbConfig.GetDSN()), c)
case config.DatabaseTypePostgreSQL:
db, err = gorm.Open(postgres.Open(dbConfig.GetDSN()), c)
default:
return fmt.Errorf("unsupported database type: %s", dbConfig.Type)
}
if err != nil {
return err
}
@ -141,6 +249,9 @@ func InitDB(dbPath string) error {
}
isUsersEmpty, err := isTableEmpty("users")
if err != nil {
return err
}
if err := initUser(); err != nil {
return err
@ -148,6 +259,50 @@ func InitDB(dbPath string) error {
return runSeeders(isUsersEmpty)
}
// TestDatabaseConnection tests database connection with provided configuration
func TestDatabaseConnection(dbConfig *config.DatabaseConfig) error {
// Validate configuration
if err := dbConfig.ValidateConfig(); err != nil {
return err
}
var gormLogger logger.Interface
if config.IsDebug() {
gormLogger = logger.Default
} else {
gormLogger = logger.Discard
}
c := &gorm.Config{
Logger: gormLogger,
}
// Test database connection based on type
var testDB *gorm.DB
var err error
switch dbConfig.Type {
case config.DatabaseTypeSQLite:
testDB, err = gorm.Open(sqlite.Open(dbConfig.GetDSN()), c)
case config.DatabaseTypePostgreSQL:
testDB, err = gorm.Open(postgres.Open(dbConfig.GetDSN()), c)
default:
return fmt.Errorf("unsupported database type: %s", dbConfig.Type)
}
if err != nil {
return err
}
// Test the connection
sqlDB, err := testDB.DB()
if err != nil {
return err
}
defer sqlDB.Close()
return sqlDB.Ping()
}
func CloseDB() error {
if db != nil {
sqlDB, err := db.DB()

View file

@ -0,0 +1,122 @@
version: '3.8'
networks:
x-ui-network:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
volumes:
postgres_data:
driver: local
x-ui-data:
driver: local
x-ui-cert:
driver: local
services:
postgres:
image: postgres:16-alpine
container_name: x-ui-postgres
hostname: postgres
restart: unless-stopped
environment:
POSTGRES_DB: ${DB_NAME:-x_ui}
POSTGRES_USER: ${DB_USER:-x_ui}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C"
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init-postgres.sql:/docker-entrypoint-initdb.d/init.sql:ro
networks:
x-ui-network:
ipv4_address: 172.20.0.2
ports:
- "127.0.0.1:5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-x_ui} -d ${DB_NAME:-x_ui}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
command: >
postgres
-c max_connections=200
-c shared_buffers=256MB
-c effective_cache_size=1GB
-c maintenance_work_mem=64MB
-c checkpoint_completion_target=0.9
-c wal_buffers=16MB
-c default_statistics_target=100
-c random_page_cost=1.1
-c effective_io_concurrency=200
-c work_mem=4MB
-c min_wal_size=1GB
-c max_wal_size=4GB
3x-ui:
image: ghcr.io/mhsanaei/3x-ui:latest
container_name: 3x-ui
hostname: 3x-ui
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
XRAY_VMESS_AEAD_FORCED: "false"
XUI_ENABLE_FAIL2BAN: "true"
DB_TYPE: "postgres"
DB_HOST: "postgres"
DB_PORT: "5432"
DB_NAME: ${DB_NAME:-x_ui}
DB_USER: ${DB_USER:-x_ui}
DB_PASSWORD: ${DB_PASSWORD}
DB_SSLMODE: ${DB_SSLMODE:-disable}
DB_TIMEZONE: ${DB_TIMEZONE:-UTC}
volumes:
- x-ui-data:/etc/x-ui/
- x-ui-cert:/root/cert/
networks:
x-ui-network:
ipv4_address: 172.20.0.3
ports:
- "${XUI_PORT:-2053}:2053"
- "${XUI_SUB_PORT:-2096}:2096"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:2053/login"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
sysctls:
- net.ipv4.ip_forward=1
- net.ipv6.conf.all.forwarding=1
cap_add:
- NET_ADMIN
security_opt:
- no-new-privileges:true
# Optional: PostgreSQL Admin Interface
pgadmin:
image: dpage/pgadmin4:latest
container_name: x-ui-pgadmin
hostname: pgadmin
restart: unless-stopped
profiles:
- admin
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL:-admin@example.com}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD:-admin}
PGADMIN_CONFIG_SERVER_MODE: 'False'
volumes:
- ./pgadmin_servers.json:/pgadmin4/servers.json:ro
networks:
x-ui-network:
ipv4_address: 172.20.0.4
ports:
- "127.0.0.1:5050:80"
depends_on:
postgres:
condition: service_healthy

View file

@ -1,14 +1,57 @@
version: '3.8'
networks:
default:
name: x-ui-network
driver: bridge
volumes:
x-ui-data:
driver: local
x-ui-cert:
driver: local
services:
3x-ui:
image: ghcr.io/mhsanaei/3x-ui:latest
container_name: 3x-ui
hostname: yourhostname
volumes:
- $PWD/db/:/etc/x-ui/
- $PWD/cert/:/root/cert/
environment:
XRAY_VMESS_AEAD_FORCED: "false"
XUI_ENABLE_FAIL2BAN: "true"
tty: true
network_mode: host
hostname: ${HOSTNAME:-3x-ui}
restart: unless-stopped
environment:
XRAY_VMESS_AEAD_FORCED: ${XRAY_VMESS_AEAD_FORCED:-false}
XUI_ENABLE_FAIL2BAN: ${XUI_ENABLE_FAIL2BAN:-true}
# Database configuration (defaults to SQLite)
DB_TYPE: ${DB_TYPE:-sqlite}
DB_HOST: ${DB_HOST:-localhost}
DB_PORT: ${DB_PORT:-5432}
DB_NAME: ${DB_NAME:-x_ui}
DB_USER: ${DB_USER:-x_ui}
DB_PASSWORD: ${DB_PASSWORD}
DB_SSLMODE: ${DB_SSLMODE:-disable}
DB_TIMEZONE: ${DB_TIMEZONE:-UTC}
volumes:
# Use named volumes for better data management
- x-ui-data:/etc/x-ui/
- x-ui-cert:/root/cert/
# Fallback to bind mounts for compatibility
# - $PWD/db/:/etc/x-ui/
# - $PWD/cert/:/root/cert/
ports:
- "${XUI_PORT:-2053}:2053"
- "${XUI_SUB_PORT:-2096}:2096"
# Use host network for simple setups (comment out ports above if using this)
# network_mode: host
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:2053/login"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
sysctls:
- net.ipv4.ip_forward=1
- net.ipv6.conf.all.forwarding=1
cap_add:
- NET_ADMIN
security_opt:
- no-new-privileges:true
tty: true

67
env.example Normal file
View file

@ -0,0 +1,67 @@
# =============================================================================
# 3X-UI Docker Environment Configuration
# =============================================================================
# Copy this file to .env and modify the values according to your setup
# =============================================================================
# General Configuration
# =============================================================================
HOSTNAME=3x-ui
XUI_PORT=2053
XUI_SUB_PORT=2096
# =============================================================================
# X-UI Configuration
# =============================================================================
XRAY_VMESS_AEAD_FORCED=false
XUI_ENABLE_FAIL2BAN=true
# =============================================================================
# Database Configuration
# =============================================================================
# Database type: sqlite or postgres
DB_TYPE=sqlite
# PostgreSQL Configuration (only needed if DB_TYPE=postgres)
DB_HOST=postgres
DB_PORT=5432
DB_NAME=x_ui
DB_USER=x_ui
# IMPORTANT: Change this password for production!
DB_PASSWORD=your_secure_password_here
DB_SSLMODE=disable
DB_TIMEZONE=UTC
# =============================================================================
# PostgreSQL Admin (PgAdmin) Configuration
# =============================================================================
# Only used with docker-compose.postgresql.yml and --profile admin
PGADMIN_EMAIL=admin@example.com
PGADMIN_PASSWORD=admin_password
# =============================================================================
# Example configurations for different setups:
# =============================================================================
# For SQLite (default, simple setup):
# DB_TYPE=sqlite
# For PostgreSQL (production setup):
# DB_TYPE=postgres
# DB_HOST=postgres
# DB_PORT=5432
# DB_NAME=x_ui
# DB_USER=x_ui
# DB_PASSWORD=your_very_secure_password_123
# DB_SSLMODE=require
# DB_TIMEZONE=UTC
# For external PostgreSQL:
# DB_TYPE=postgres
# DB_HOST=your-postgres-server.com
# DB_PORT=5432
# DB_NAME=x_ui_production
# DB_USER=x_ui_user
# DB_PASSWORD=your_external_db_password
# DB_SSLMODE=require
# DB_TIMEZONE=America/New_York

5
go.mod
View file

@ -22,6 +22,7 @@ require (
golang.org/x/crypto v0.38.0
golang.org/x/text v0.25.0
google.golang.org/grpc v1.72.1
gorm.io/driver/postgres v1.5.9
gorm.io/driver/sqlite v1.5.7
gorm.io/gorm v1.25.12
)
@ -49,6 +50,10 @@ require (
github.com/gorilla/sessions v1.4.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grbit/go-json v0.11.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect

10
go.sum
View file

@ -80,6 +80,14 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grbit/go-json v0.11.0 h1:bAbyMdYrYl/OjYsSqLH99N2DyQ291mHy726Mx+sYrnc=
github.com/grbit/go-json v0.11.0/go.mod h1:IYpHsdybQ386+6g3VE6AXQ3uTGa5mquBme5/ZWmtzek=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
@ -261,6 +269,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=

46
init-postgres.sql Normal file
View file

@ -0,0 +1,46 @@
-- =============================================================================
-- 3X-UI PostgreSQL Database Initialization Script
-- =============================================================================
-- This script is automatically executed when PostgreSQL container starts
-- for the first time
-- Create extensions if needed
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_stat_statements";
-- Set timezone
SET timezone = 'UTC';
-- Create additional indexes for better performance (will be created by GORM if needed)
-- These are just examples, actual tables will be created by the application
-- Grant additional permissions to the user
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO x_ui;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO x_ui;
GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public TO x_ui;
-- Set default privileges for future objects
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO x_ui;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO x_ui;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON FUNCTIONS TO x_ui;
-- Optimize PostgreSQL settings for 3X-UI workload
-- These settings are also configured in docker-compose.postgresql.yml
ALTER SYSTEM SET shared_preload_libraries = 'pg_stat_statements';
ALTER SYSTEM SET log_statement = 'mod';
ALTER SYSTEM SET log_min_duration_statement = 1000;
ALTER SYSTEM SET log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h ';
-- Create a simple health check function
CREATE OR REPLACE FUNCTION health_check()
RETURNS TEXT AS $$
BEGIN
RETURN 'OK - ' || current_timestamp;
END;
$$ LANGUAGE plpgsql;
-- Log initialization completion
DO $$
BEGIN
RAISE NOTICE '3X-UI PostgreSQL database initialized successfully at %', current_timestamp;
END $$;

View file

@ -81,6 +81,145 @@ gen_random_string() {
echo "$random_string"
}
install_postgresql() {
echo -e "${green}Installing PostgreSQL...${plain}"
case "${release}" in
ubuntu | debian | armbian)
apt-get update
apt-get install -y postgresql postgresql-contrib
;;
centos | almalinux | rocky | ol)
yum install -y postgresql-server postgresql-contrib
postgresql-setup initdb
;;
fedora | amzn | virtuozzo)
dnf install -y postgresql-server postgresql-contrib
postgresql-setup --initdb
;;
arch | manjaro | parch)
pacman -S --noconfirm postgresql
sudo -u postgres initdb -D /var/lib/postgres/data
;;
opensuse-tumbleweed)
zypper install -y postgresql-server postgresql-contrib
;;
*)
echo -e "${red}Unsupported OS for PostgreSQL installation${plain}"
return 1
;;
esac
# Start and enable PostgreSQL service
systemctl start postgresql
systemctl enable postgresql
if ! systemctl is-active --quiet postgresql; then
echo -e "${red}Failed to start PostgreSQL service${plain}"
return 1
fi
echo -e "${green}PostgreSQL installed and started successfully${plain}"
return 0
}
setup_postgresql_for_xui() {
echo -e "${green}Setting up PostgreSQL for x-ui...${plain}"
local db_name="x_ui"
local db_user="x_ui"
local db_password=$(gen_random_string 16)
# Create database and user
sudo -u postgres psql -c "CREATE DATABASE ${db_name};" 2>/dev/null || true
sudo -u postgres psql -c "CREATE USER ${db_user} WITH PASSWORD '${db_password}';" 2>/dev/null || true
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE ${db_name} TO ${db_user};" 2>/dev/null || true
sudo -u postgres psql -c "ALTER USER ${db_user} CREATEDB;" 2>/dev/null || true
# Create environment file for x-ui
cat > /etc/x-ui/db.env << EOF
DB_TYPE=postgres
DB_HOST=localhost
DB_PORT=5432
DB_NAME=${db_name}
DB_USER=${db_user}
DB_PASSWORD=${db_password}
DB_SSLMODE=disable
DB_TIMEZONE=UTC
EOF
chmod 600 /etc/x-ui/db.env
echo -e "${green}PostgreSQL setup completed${plain}"
echo -e "${yellow}Database: ${db_name}${plain}"
echo -e "${yellow}User: ${db_user}${plain}"
echo -e "${yellow}Password: ${db_password}${plain}"
echo -e "${yellow}Configuration saved to: /etc/x-ui/db.env${plain}"
return 0
}
database_setup() {
echo -e "${green}Database Setup${plain}"
echo -e "Choose your database type:"
echo -e "${green}1.${plain} SQLite (Default, recommended for most users)"
echo -e "${green}2.${plain} PostgreSQL (For high-load environments)"
read -rp "Enter your choice [1-2, default: 1]: " db_choice
case "${db_choice}" in
2)
echo -e "${yellow}Setting up PostgreSQL...${plain}"
# Check if PostgreSQL is already installed
if command -v psql &> /dev/null && systemctl is-active --quiet postgresql; then
echo -e "${yellow}PostgreSQL is already installed and running${plain}"
read -rp "Do you want to use existing PostgreSQL installation? [y/n, default: y]: " use_existing
if [[ "${use_existing}" == "n" || "${use_existing}" == "N" ]]; then
return 1
fi
else
read -rp "PostgreSQL will be installed. Continue? [y/n, default: y]: " install_confirm
if [[ "${install_confirm}" == "n" || "${install_confirm}" == "N" ]]; then
echo -e "${yellow}Using SQLite instead${plain}"
return 0
fi
if ! install_postgresql; then
echo -e "${red}Failed to install PostgreSQL. Using SQLite instead${plain}"
return 0
fi
fi
if ! setup_postgresql_for_xui; then
echo -e "${red}Failed to setup PostgreSQL. Using SQLite instead${plain}"
return 0
fi
echo -e "${green}PostgreSQL setup completed successfully${plain}"
;;
1|"")
echo -e "${green}Using SQLite database (default)${plain}"
# Create empty env file to indicate SQLite usage
mkdir -p /etc/x-ui
cat > /etc/x-ui/db.env << EOF
DB_TYPE=sqlite
EOF
chmod 600 /etc/x-ui/db.env
;;
*)
echo -e "${yellow}Invalid choice. Using SQLite (default)${plain}"
mkdir -p /etc/x-ui
cat > /etc/x-ui/db.env << EOF
DB_TYPE=sqlite
EOF
chmod 600 /etc/x-ui/db.env
;;
esac
return 0
}
config_after_install() {
local existing_hasDefaultCredential=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'hasDefaultCredential: .+' | awk '{print $2}')
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
@ -193,6 +332,10 @@ install_x-ui() {
wget -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
chmod +x /usr/local/x-ui/x-ui.sh
chmod +x /usr/bin/x-ui
# Setup database before configuration
database_setup
config_after_install
systemctl daemon-reload
@ -200,24 +343,25 @@ install_x-ui() {
systemctl start x-ui
echo -e "${green}x-ui ${tag_version}${plain} installation finished, it is running now..."
echo -e ""
echo -e "┌───────────────────────────────────────────────────────┐
${blue}x-ui control menu usages (subcommands):${plain}
│ │
${blue}x-ui${plain} - Admin Management Script │
${blue}x-ui start${plain} - Start │
${blue}x-ui stop${plain} - Stop │
${blue}x-ui restart${plain} - Restart │
${blue}x-ui status${plain} - Current Status │
${blue}x-ui settings${plain} - Current Settings │
${blue}x-ui enable${plain} - Enable Autostart on OS Startup │
${blue}x-ui disable${plain} - Disable Autostart on OS Startup │
${blue}x-ui log${plain} - Check logs │
${blue}x-ui banlog${plain} - Check Fail2ban ban logs │
${blue}x-ui update${plain} - Update │
${blue}x-ui legacy${plain} - legacy version │
${blue}x-ui install${plain} - Install │
${blue}x-ui uninstall${plain} - Uninstall │
└───────────────────────────────────────────────────────┘"
echo -e "┌───────────────────────────────────────────────────────┐"
echo -e "${blue}x-ui control menu usages (subcommands):${plain}"
echo -e "│ │"
echo -e "${blue}x-ui${plain} - Admin Management Script │"
echo -e "${blue}x-ui start${plain} - Start │"
echo -e "${blue}x-ui stop${plain} - Stop │"
echo -e "${blue}x-ui restart${plain} - Restart │"
echo -e "${blue}x-ui status${plain} - Current Status │"
echo -e "${blue}x-ui settings${plain} - Current Settings │"
echo -e "${blue}x-ui enable${plain} - Enable Autostart on OS Startup │"
echo -e "${blue}x-ui disable${plain} - Disable Autostart on OS Startup │"
echo -e "${blue}x-ui log${plain} - Check logs │"
echo -e "${blue}x-ui banlog${plain} - Check Fail2ban ban logs │"
echo -e "${blue}x-ui update${plain} - Update │"
echo -e "${blue}x-ui legacy${plain} - legacy version │"
echo -e "${blue}x-ui install${plain} - Install │"
echo -e "${blue}x-ui uninstall${plain} - Uninstall │"
echo -e "${blue}x-ui database${plain} - Database Management │"
echo -e "└───────────────────────────────────────────────────────┘"
}
echo -e "${green}Running...${plain}"

23
pgadmin_servers.json Normal file
View file

@ -0,0 +1,23 @@
{
"Servers": {
"1": {
"Name": "3X-UI PostgreSQL",
"Group": "Servers",
"Host": "postgres",
"Port": 5432,
"MaintenanceDB": "x_ui",
"Username": "x_ui",
"PassFile": "/tmp/pgpassfile",
"SSLMode": "disable",
"SSLCert": "<STORAGE_DIR>/.postgresql/postgresql.crt",
"SSLKey": "<STORAGE_DIR>/.postgresql/postgresql.key",
"SSLCompression": 0,
"Timeout": 10,
"UseSSHTunnel": 0,
"TunnelHost": "",
"TunnelPort": "22",
"TunnelUsername": "",
"TunnelAuthentication": 0
}
}
}

17
wait-for-postgres.sh Normal file
View file

@ -0,0 +1,17 @@
#!/bin/bash
set -e
host="$1"
port="$2"
user="$3"
database="$4"
shift 4
cmd="$@"
until PGPASSWORD="$DB_PASSWORD" psql -h "$host" -p "$port" -U "$user" -d "$database" -c '\q'; do
>&2 echo "PostgreSQL is unavailable - sleeping"
sleep 1
done
>&2 echo "PostgreSQL is up - executing command"
exec $cmd

View file

@ -46,7 +46,14 @@ class AllSetting {
this.subJsonNoises = "";
this.subJsonMux = "";
this.subJsonRules = "";
this.dbType = "sqlite";
this.dbHost = "localhost";
this.dbPort = 5432;
this.dbName = "x_ui";
this.dbUser = "x_ui";
this.dbPassword = "";
this.dbSSLMode = "disable";
this.dbTimeZone = "UTC";
this.timeLocation = "Local";
if (data == null) {

View file

@ -4,6 +4,7 @@ import (
"errors"
"time"
"x-ui/database"
"x-ui/util/crypto"
"x-ui/web/entity"
"x-ui/web/service"
@ -40,6 +41,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
g.POST("/updateUser", a.updateUser)
g.POST("/restartPanel", a.restartPanel)
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
g.POST("/testDatabaseConnection", a.testDatabaseConnection)
}
func (a *SettingController) getAllSetting(c *gin.Context) {
@ -109,3 +111,19 @@ func (a *SettingController) getDefaultXrayConfig(c *gin.Context) {
}
jsonObj(c, defaultJsonConfig, nil)
}
func (a *SettingController) testDatabaseConnection(c *gin.Context) {
dbConfig, err := a.settingService.GetDatabaseConfig()
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.testDatabaseConnection"), err)
return
}
err = database.TestDatabaseConnection(dbConfig)
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.testDatabaseConnection"), err)
return
}
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.testDatabaseConnectionSuccess"), nil)
}

View file

@ -61,6 +61,14 @@ type AllSetting struct {
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"`
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
Datepicker string `json:"datepicker" form:"datepicker"`
DbType string `json:"dbType" form:"dbType"`
DbHost string `json:"dbHost" form:"dbHost"`
DbPort int `json:"dbPort" form:"dbPort"`
DbName string `json:"dbName" form:"dbName"`
DbUser string `json:"dbUser" form:"dbUser"`
DbPassword string `json:"dbPassword" form:"dbPassword"`
DbSSLMode string `json:"dbSSLMode" form:"dbSSLMode"`
DbTimeZone string `json:"dbTimeZone" form:"dbTimeZone"`
}
func (s *AllSetting) CheckValid() error {

View file

@ -115,6 +115,9 @@
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable" :style="{ paddingTop: '20px' }">
{{ template "settings/panel/subscription/json" . }}
</a-tab-pane>
<a-tab-pane key="6" tab='{{ i18n "pages.settings.databaseSettings" }}' :style="{ paddingTop: '20px' }">
{{ template "settings/panel/database" . }}
</a-tab-pane>
</a-tabs>
</a-space>
</a-spin>

View file

@ -0,0 +1,86 @@
{{define "settings/panel/database"}}
<a-collapse default-active-key="1">
<a-collapse-panel key="1" header='{{ i18n "pages.settings.databaseSettings"}}'>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.databaseType"}}</template>
<template #description>{{ i18n "pages.settings.databaseTypeDesc"}}</template>
<template #control>
<a-select v-model="allSetting.dbType" :style="{ width: '100%' }">
<a-select-option value="sqlite">SQLite</a-select-option>
<a-select-option value="postgres">PostgreSQL</a-select-option>
</a-select>
</template>
</a-setting-list-item>
<template v-if="allSetting.dbType === 'postgres'">
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.databaseHost"}}</template>
<template #description>{{ i18n "pages.settings.databaseHostDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.dbHost" placeholder="localhost"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.databasePort"}}</template>
<template #description>{{ i18n "pages.settings.databasePortDesc"}}</template>
<template #control>
<a-input-number v-model="allSetting.dbPort" :min="1" :max="65535" :style="{ width: '100%' }"></a-input-number>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.databaseName"}}</template>
<template #description>{{ i18n "pages.settings.databaseNameDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.dbName" placeholder="x_ui"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.databaseUser"}}</template>
<template #description>{{ i18n "pages.settings.databaseUserDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.dbUser" placeholder="x_ui"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.databasePassword"}}</template>
<template #description>{{ i18n "pages.settings.databasePasswordDesc"}}</template>
<template #control>
<a-input type="password" v-model="allSetting.dbPassword"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.databaseSSLMode"}}</template>
<template #description>{{ i18n "pages.settings.databaseSSLModeDesc"}}</template>
<template #control>
<a-select v-model="allSetting.dbSSLMode" :style="{ width: '100%' }">
<a-select-option value="disable">Disable</a-select-option>
<a-select-option value="require">Require</a-select-option>
<a-select-option value="verify-ca">Verify CA</a-select-option>
<a-select-option value="verify-full">Verify Full</a-select-option>
</a-select>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.databaseTimeZone"}}</template>
<template #description>{{ i18n "pages.settings.databaseTimeZoneDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.dbTimeZone" placeholder="UTC"></a-input>
</template>
</a-setting-list-item>
</template>
<a-alert type="warning" :style="{ marginTop: '20px' }">
<template slot="message">
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
<span>{{ i18n "pages.settings.databaseWarning" }}</span>
</template>
</a-alert>
</a-collapse-panel>
</a-collapse>
{{end}}

View file

@ -10,6 +10,7 @@ import (
"strings"
"time"
"x-ui/config"
"x-ui/database"
"x-ui/database/model"
"x-ui/logger"
@ -72,6 +73,14 @@ var defaultValueMap = map[string]string{
"warp": "",
"externalTrafficInformEnable": "false",
"externalTrafficInformURI": "",
"dbType": "sqlite",
"dbHost": "localhost",
"dbPort": "5432",
"dbName": "x_ui",
"dbUser": "x_ui",
"dbPassword": "",
"dbSSLMode": "disable",
"dbTimeZone": "UTC",
}
type SettingService struct{}
@ -519,6 +528,131 @@ func (s *SettingService) SetExternalTrafficInformURI(InformURI string) error {
return s.setString("externalTrafficInformURI", InformURI)
}
// Database configuration methods
func (s *SettingService) GetDbType() (string, error) {
return s.getString("dbType")
}
func (s *SettingService) SetDbType(dbType string) error {
return s.setString("dbType", dbType)
}
func (s *SettingService) GetDbHost() (string, error) {
return s.getString("dbHost")
}
func (s *SettingService) SetDbHost(host string) error {
return s.setString("dbHost", host)
}
func (s *SettingService) GetDbPort() (int, error) {
return s.getInt("dbPort")
}
func (s *SettingService) SetDbPort(port int) error {
return s.setInt("dbPort", port)
}
func (s *SettingService) GetDbName() (string, error) {
return s.getString("dbName")
}
func (s *SettingService) SetDbName(name string) error {
return s.setString("dbName", name)
}
func (s *SettingService) GetDbUser() (string, error) {
return s.getString("dbUser")
}
func (s *SettingService) SetDbUser(user string) error {
return s.setString("dbUser", user)
}
func (s *SettingService) GetDbPassword() (string, error) {
return s.getString("dbPassword")
}
func (s *SettingService) SetDbPassword(password string) error {
return s.setString("dbPassword", password)
}
func (s *SettingService) GetDbSSLMode() (string, error) {
return s.getString("dbSSLMode")
}
func (s *SettingService) SetDbSSLMode(sslMode string) error {
return s.setString("dbSSLMode", sslMode)
}
func (s *SettingService) GetDbTimeZone() (string, error) {
return s.getString("dbTimeZone")
}
func (s *SettingService) SetDbTimeZone(timeZone string) error {
return s.setString("dbTimeZone", timeZone)
}
// GetDatabaseConfig returns database configuration from settings
func (s *SettingService) GetDatabaseConfig() (*config.DatabaseConfig, error) {
dbType, err := s.GetDbType()
if err != nil {
return nil, err
}
dbConfig := &config.DatabaseConfig{
Type: config.DatabaseType(dbType),
}
if dbConfig.Type == config.DatabaseTypePostgreSQL {
host, err := s.GetDbHost()
if err != nil {
return nil, err
}
port, err := s.GetDbPort()
if err != nil {
return nil, err
}
name, err := s.GetDbName()
if err != nil {
return nil, err
}
user, err := s.GetDbUser()
if err != nil {
return nil, err
}
password, err := s.GetDbPassword()
if err != nil {
return nil, err
}
sslMode, err := s.GetDbSSLMode()
if err != nil {
return nil, err
}
timeZone, err := s.GetDbTimeZone()
if err != nil {
return nil, err
}
dbConfig.Postgres = config.PostgresConfig{
Host: host,
Port: port,
Database: name,
Username: user,
Password: password,
SSLMode: sslMode,
TimeZone: timeZone,
}
} else {
// For SQLite, use default path
dbConfig.SQLite = config.SQLiteConfig{
Path: config.GetDefaultDatabaseConfig().SQLite.Path,
}
}
return dbConfig, nil
}
func (s *SettingService) GetIpLimitEnable() (bool, error) {
accessLogPath, err := xray.GetAccessLogPath()
if err != nil {

View file

@ -293,11 +293,11 @@
"panelPort" = "Listen Port"
"panelPortDesc" = "The port number for the web panel. (must be an unused port)"
"publicKeyPath" = "Public Key Path"
"publicKeyPathDesc" = "The public key file path for the web panel. (begins with /)"
"publicKeyPathDesc" = "The public key file path for the web panel. (begins with '//')"
"privateKeyPath" = "Private Key Path"
"privateKeyPathDesc" = "The private key file path for the web panel. (begins with /)"
"privateKeyPathDesc" = "The private key file path for the web panel. (begins with '//')"
"panelUrlPath" = "URI Path"
"panelUrlPathDesc" = "The URI path for the web panel. (begins with / and concludes with /)"
"panelUrlPathDesc" = "The URI path for the web panel. (begins with '//' and concludes with '//')"
"pageSize" = "Pagination Size"
"pageSizeDesc" = "Define page size for inbounds table. (0 = disable)"
"remarkModel" = "Remark Model & Separation Character"
@ -345,11 +345,11 @@
"subPort" = "Listen Port"
"subPortDesc" = "The port number for the subscription service. (must be an unused port)"
"subCertPath" = "Public Key Path"
"subCertPathDesc" = "The public key file path for the subscription service. (begins with /)"
"subCertPathDesc" = "The public key file path for the subscription service. (begins with '//')"
"subKeyPath" = "Private Key Path"
"subKeyPathDesc" = "The private key file path for the subscription service. (begins with /)"
"subKeyPathDesc" = "The private key file path for the subscription service. (begins with '//')"
"subPath" = "URI Path"
"subPathDesc" = "The URI path for the subscription service. (begins with / and concludes with /)"
"subPathDesc" = "The URI path for the subscription service. (begins with '//' and concludes with '//')"
"subDomain" = "Listen Domain"
"subDomainDesc" = "The domain name for the subscription service. (leave blank to listen on all domains and IPs)"
"subUpdates" = "Update Intervals"
@ -551,6 +551,28 @@
"userPassMustBeNotEmpty" = "The new username and password is empty"
"getOutboundTrafficError" = "Error getting traffics"
"resetOutboundTrafficError" = "Error in reset outbound traffics"
"testDatabaseConnection" = "Failed to test database connection"
"testDatabaseConnectionSuccess" = "Database connection test successful"
[pages.settings.database]
"databaseSettings" = "Database Settings"
"databaseType" = "Database Type"
"databaseTypeDesc" = "Choose between SQLite (default) or PostgreSQL database"
"databaseHost" = "Database Host"
"databaseHostDesc" = "PostgreSQL server hostname or IP address"
"databasePort" = "Database Port"
"databasePortDesc" = "PostgreSQL server port (default: 5432)"
"databaseName" = "Database Name"
"databaseNameDesc" = "Name of the PostgreSQL database"
"databaseUser" = "Database User"
"databaseUserDesc" = "PostgreSQL username for authentication"
"databasePassword" = "Database Password"
"databasePasswordDesc" = "PostgreSQL password for authentication"
"databaseSSLMode" = "SSL Mode"
"databaseSSLModeDesc" = "PostgreSQL SSL connection mode"
"databaseTimeZone" = "Time Zone"
"databaseTimeZoneDesc" = "Database timezone (default: UTC)"
"databaseWarning" = "Warning: Changing database settings requires panel restart to take effect"
[tgbot]
"keyboardClosed" = "❌ Custom keyboard closed!"

View file

@ -554,6 +554,28 @@
"userPassMustBeNotEmpty" = "El nuevo nombre de usuario y la nueva contraseña no pueden estar vacíos"
"getOutboundTrafficError" = "Error al obtener el tráfico saliente"
"resetOutboundTrafficError" = "Error al reiniciar el tráfico saliente"
"testDatabaseConnection" = "Error al probar la conexión de la base de datos"
"testDatabaseConnectionSuccess" = "Prueba de conexión de base de datos exitosa"
[pages.settings.database]
"databaseSettings" = "Configuración de Base de Datos"
"databaseType" = "Tipo de Base de Datos"
"databaseTypeDesc" = "Elija entre SQLite (predeterminado) o PostgreSQL"
"databaseHost" = "Host de Base de Datos"
"databaseHostDesc" = "Nombre del host o dirección IP del servidor PostgreSQL"
"databasePort" = "Puerto de Base de Datos"
"databasePortDesc" = "Puerto del servidor PostgreSQL (predeterminado: 5432)"
"databaseName" = "Nombre de Base de Datos"
"databaseNameDesc" = "Nombre de la base de datos PostgreSQL"
"databaseUser" = "Usuario de Base de Datos"
"databaseUserDesc" = "Nombre de usuario PostgreSQL para autenticación"
"databasePassword" = "Contraseña de Base de Datos"
"databasePasswordDesc" = "Contraseña PostgreSQL para autenticación"
"databaseSSLMode" = "Modo SSL"
"databaseSSLModeDesc" = "Modo de conexión SSL de PostgreSQL"
"databaseTimeZone" = "Zona Horaria"
"databaseTimeZoneDesc" = "Zona horaria de la base de datos (predeterminado: UTC)"
"databaseWarning" = "Advertencia: Cambiar la configuración de la base de datos requiere reiniciar el panel"
[tgbot]
"keyboardClosed" = "❌ ¡Teclado personalizado cerrado!"

View file

@ -218,7 +218,7 @@
"IPLimitlogclear" = "清除日志"
"setDefaultCert" = "从面板设置证书"
"telegramDesc" = "请提供Telegram聊天ID。在机器人中使用'/id'命令)或(@userinfobot"
"subscriptionDesc" = "要找到你的订阅 URL请导航到“详细信息”。此外,你可以为多个客户端使用相同的名称。"
"subscriptionDesc" = "要找到你的订阅 URL请导航到""。此外,你可以为多个客户端使用相同的名称。"
"info" = "信息"
"same" = "相同"
"inboundData" = "入站数据"
@ -554,6 +554,28 @@
"userPassMustBeNotEmpty" = "新用户名和新密码不能为空"
"getOutboundTrafficError" = "获取出站流量错误"
"resetOutboundTrafficError" = "重置出站流量错误"
"testDatabaseConnection" = "数据库连接测试失败"
"testDatabaseConnectionSuccess" = "数据库连接测试成功"
[pages.settings.database]
"databaseSettings" = "数据库设置"
"databaseType" = "数据库类型"
"databaseTypeDesc" = "选择 SQLite默认或 PostgreSQL 数据库"
"databaseHost" = "数据库主机"
"databaseHostDesc" = "PostgreSQL 服务器主机名或 IP 地址"
"databasePort" = "数据库端口"
"databasePortDesc" = "PostgreSQL 服务器端口默认5432"
"databaseName" = "数据库名称"
"databaseNameDesc" = "PostgreSQL 数据库名称"
"databaseUser" = "数据库用户"
"databaseUserDesc" = "PostgreSQL 认证用户名"
"databasePassword" = "数据库密码"
"databasePasswordDesc" = "PostgreSQL 认证密码"
"databaseSSLMode" = "SSL 模式"
"databaseSSLModeDesc" = "PostgreSQL SSL 连接模式"
"databaseTimeZone" = "时区"
"databaseTimeZoneDesc" = "数据库时区默认UTC"
"databaseWarning" = "警告:更改数据库设置需要重启面板才能生效"
[tgbot]
"keyboardClosed" = "❌ 自定义键盘已关闭!"

42
x-ui-postgresql.service Normal file
View file

@ -0,0 +1,42 @@
[Unit]
Description=PostgreSQL database server for 3x-ui
Documentation=man:postgres(1)
After=network.target
Wants=network.target
Before=x-ui.service
[Service]
Type=notify
User=postgres
Group=postgres
# PostgreSQL data directory
Environment=PGDATA=/var/lib/postgresql/data
# PostgreSQL configuration
ExecStart=/usr/bin/postgres -D ${PGDATA}
ExecReload=/bin/kill -HUP $MAINPID
# Process management
KillMode=mixed
KillSignal=SIGINT
TimeoutSec=0
# Restart policy
Restart=on-failure
RestartSec=5s
# Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/postgresql
# Resource limits
LimitNOFILE=65536
LimitNPROC=4096
[Install]
WantedBy=multi-user.target
WantedBy=x-ui.service

437
x-ui.sh
View file

@ -1620,6 +1620,319 @@ remove_iplimit() {
esac
}
install_postgresql() {
echo -e "${green}Installing PostgreSQL...${plain}"
case "${release}" in
ubuntu | debian | armbian)
apt-get update
apt-get install -y postgresql postgresql-contrib
;;
centos | almalinux | rocky | ol)
yum install -y postgresql-server postgresql-contrib
postgresql-setup initdb
;;
fedora | amzn | virtuozzo)
dnf install -y postgresql-server postgresql-contrib
postgresql-setup --initdb
;;
arch | manjaro | parch)
pacman -S --noconfirm postgresql
sudo -u postgres initdb -D /var/lib/postgres/data
;;
opensuse-tumbleweed)
zypper install -y postgresql-server postgresql-contrib
;;
*)
echo -e "${red}Unsupported OS for PostgreSQL installation${plain}"
return 1
;;
esac
# Start and enable PostgreSQL service
systemctl start postgresql
systemctl enable postgresql
if ! systemctl is-active --quiet postgresql; then
echo -e "${red}Failed to start PostgreSQL service${plain}"
return 1
fi
echo -e "${green}PostgreSQL installed and started successfully${plain}"
return 0
}
setup_postgresql_for_xui() {
echo -e "${green}Setting up PostgreSQL for x-ui...${plain}"
local db_name="x_ui"
local db_user="x_ui"
local db_password=$(gen_random_string 16)
# Create database and user
sudo -u postgres psql -c "CREATE DATABASE ${db_name};" 2>/dev/null || true
sudo -u postgres psql -c "CREATE USER ${db_user} WITH PASSWORD '${db_password}';" 2>/dev/null || true
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE ${db_name} TO ${db_user};" 2>/dev/null || true
sudo -u postgres psql -c "ALTER USER ${db_user} CREATEDB;" 2>/dev/null || true
# Create environment file for x-ui
mkdir -p /etc/x-ui
cat > /etc/x-ui/db.env << EOF
DB_TYPE=postgres
DB_HOST=localhost
DB_PORT=5432
DB_NAME=${db_name}
DB_USER=${db_user}
DB_PASSWORD=${db_password}
DB_SSLMODE=disable
DB_TIMEZONE=UTC
EOF
chmod 600 /etc/x-ui/db.env
echo -e "${green}PostgreSQL setup completed${plain}"
echo -e "${yellow}Database: ${db_name}${plain}"
echo -e "${yellow}User: ${db_user}${plain}"
echo -e "${yellow}Password: ${db_password}${plain}"
echo -e "${yellow}Configuration saved to: /etc/x-ui/db.env${plain}"
return 0
}
switch_to_postgresql() {
echo -e "${yellow}Switching to PostgreSQL database...${plain}"
# Check if PostgreSQL is installed
if ! command -v psql &> /dev/null; then
echo -e "${yellow}PostgreSQL is not installed. Installing now...${plain}"
if ! install_postgresql; then
echo -e "${red}Failed to install PostgreSQL${plain}"
return 1
fi
fi
# Check if PostgreSQL service is running
if ! systemctl is-active --quiet postgresql; then
echo -e "${yellow}Starting PostgreSQL service...${plain}"
systemctl start postgresql
systemctl enable postgresql
fi
# Setup PostgreSQL for x-ui
if ! setup_postgresql_for_xui; then
echo -e "${red}Failed to setup PostgreSQL${plain}"
return 1
fi
echo -e "${green}Successfully switched to PostgreSQL${plain}"
echo -e "${yellow}Please restart x-ui panel to apply changes${plain}"
return 0
}
switch_to_sqlite() {
echo -e "${yellow}Switching to SQLite database...${plain}"
# Create environment file for SQLite
mkdir -p /etc/x-ui
cat > /etc/x-ui/db.env << EOF
DB_TYPE=sqlite
EOF
chmod 600 /etc/x-ui/db.env
echo -e "${green}Successfully switched to SQLite${plain}"
echo -e "${yellow}Please restart x-ui panel to apply changes${plain}"
return 0
}
backup_database() {
local backup_dir="/etc/x-ui/backups"
local timestamp=$(date +"%Y%m%d_%H%M%S")
mkdir -p "$backup_dir"
# Check current database type
if [[ -f /etc/x-ui/db.env ]]; then
source /etc/x-ui/db.env
else
DB_TYPE="sqlite"
fi
case "$DB_TYPE" in
postgres)
echo -e "${green}Backing up PostgreSQL database...${plain}"
if [[ -f /etc/x-ui/db.env ]]; then
source /etc/x-ui/db.env
PGPASSWORD="$DB_PASSWORD" pg_dump -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" > "$backup_dir/postgresql_backup_$timestamp.sql"
if [[ $? -eq 0 ]]; then
echo -e "${green}PostgreSQL backup created: $backup_dir/postgresql_backup_$timestamp.sql${plain}"
else
echo -e "${red}Failed to create PostgreSQL backup${plain}"
return 1
fi
else
echo -e "${red}Database configuration not found${plain}"
return 1
fi
;;
sqlite|*)
echo -e "${green}Backing up SQLite database...${plain}"
local sqlite_path="/etc/x-ui/x-ui.db"
if [[ -f "$sqlite_path" ]]; then
cp "$sqlite_path" "$backup_dir/sqlite_backup_$timestamp.db"
echo -e "${green}SQLite backup created: $backup_dir/sqlite_backup_$timestamp.db${plain}"
else
echo -e "${red}SQLite database not found at $sqlite_path${plain}"
return 1
fi
;;
esac
return 0
}
show_database_status() {
echo -e "${green}Database Status:${plain}"
if [[ -f /etc/x-ui/db.env ]]; then
source /etc/x-ui/db.env
echo -e "Current database type: ${green}$DB_TYPE${plain}"
case "$DB_TYPE" in
postgres)
echo -e "Host: $DB_HOST"
echo -e "Port: $DB_PORT"
echo -e "Database: $DB_NAME"
echo -e "User: $DB_USER"
# Check PostgreSQL service status
if systemctl is-active --quiet postgresql; then
echo -e "PostgreSQL service: ${green}Running${plain}"
else
echo -e "PostgreSQL service: ${red}Not Running${plain}"
fi
# Test connection
if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT 1;" &>/dev/null; then
echo -e "Database connection: ${green}OK${plain}"
else
echo -e "Database connection: ${red}Failed${plain}"
fi
;;
sqlite|*)
local sqlite_path="/etc/x-ui/x-ui.db"
if [[ -f "$sqlite_path" ]]; then
echo -e "SQLite file: ${green}$sqlite_path${plain}"
echo -e "File size: $(du -h "$sqlite_path" | cut -f1)"
else
echo -e "SQLite file: ${red}Not found${plain}"
fi
;;
esac
else
echo -e "Database configuration: ${red}Not found${plain}"
echo -e "Using default: ${green}SQLite${plain}"
fi
}
database_menu() {
echo -e "${green}\t1.${plain} Show Database Status"
echo -e "${green}\t2.${plain} Switch to PostgreSQL"
echo -e "${green}\t3.${plain} Switch to SQLite"
echo -e "${green}\t4.${plain} Install PostgreSQL"
echo -e "${green}\t5.${plain} Backup Database"
echo -e "${green}\t6.${plain} PostgreSQL Service Management"
echo -e "${green}\t0.${plain} Back to Main Menu"
read -rp "Choose an option: " choice
case "$choice" in
0)
show_menu
;;
1)
show_database_status
database_menu
;;
2)
confirm "Switch to PostgreSQL database? This will require panel restart." "y"
if [[ $? == 0 ]]; then
switch_to_postgresql
fi
database_menu
;;
3)
confirm "Switch to SQLite database? This will require panel restart." "y"
if [[ $? == 0 ]]; then
switch_to_sqlite
fi
database_menu
;;
4)
confirm "Install PostgreSQL?" "y"
if [[ $? == 0 ]]; then
install_postgresql
fi
database_menu
;;
5)
confirm "Create database backup?" "y"
if [[ $? == 0 ]]; then
backup_database
fi
database_menu
;;
6)
postgresql_service_menu
database_menu
;;
*)
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
database_menu
;;
esac
}
postgresql_service_menu() {
echo -e "${green}\t1.${plain} Start PostgreSQL"
echo -e "${green}\t2.${plain} Stop PostgreSQL"
echo -e "${green}\t3.${plain} Restart PostgreSQL"
echo -e "${green}\t4.${plain} PostgreSQL Status"
echo -e "${green}\t5.${plain} Enable PostgreSQL Autostart"
echo -e "${green}\t6.${plain} Disable PostgreSQL Autostart"
echo -e "${green}\t0.${plain} Back to Database Menu"
read -rp "Choose an option: " choice
case "$choice" in
0)
return
;;
1)
systemctl start postgresql
echo -e "${green}PostgreSQL started${plain}"
;;
2)
systemctl stop postgresql
echo -e "${green}PostgreSQL stopped${plain}"
;;
3)
systemctl restart postgresql
echo -e "${green}PostgreSQL restarted${plain}"
;;
4)
systemctl status postgresql
;;
5)
systemctl enable postgresql
echo -e "${green}PostgreSQL autostart enabled${plain}"
;;
6)
systemctl disable postgresql
echo -e "${green}PostgreSQL autostart disabled${plain}"
;;
*)
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
postgresql_service_menu
;;
esac
}
SSH_port_forwarding() {
local server_ip=$(curl -s https://api.ipify.org)
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
@ -1698,66 +2011,66 @@ SSH_port_forwarding() {
}
show_usage() {
echo -e "┌───────────────────────────────────────────────────────┐
${blue}x-ui control menu usages (subcommands):${plain}
│ │
${blue}x-ui${plain} - Admin Management Script │
${blue}x-ui start${plain} - Start │
${blue}x-ui stop${plain} - Stop │
${blue}x-ui restart${plain} - Restart │
${blue}x-ui status${plain} - Current Status │
${blue}x-ui settings${plain} - Current Settings │
${blue}x-ui enable${plain} - Enable Autostart on OS Startup │
${blue}x-ui disable${plain} - Disable Autostart on OS Startup │
${blue}x-ui log${plain} - Check logs │
${blue}x-ui banlog${plain} - Check Fail2ban ban logs │
${blue}x-ui update${plain} - Update │
${blue}x-ui legacy${plain} - legacy version │
${blue}x-ui install${plain} - Install │
${blue}x-ui uninstall${plain} - Uninstall │
└───────────────────────────────────────────────────────┘"
echo -e "┌───────────────────────────────────────────────────────┐"
echo -e "${blue}x-ui control menu usages (subcommands):${plain}"
echo -e "│ │"
echo -e "${blue}x-ui${plain} - Admin Management Script │"
echo -e "${blue}x-ui start${plain} - Start │"
echo -e "${blue}x-ui stop${plain} - Stop │"
echo -e "${blue}x-ui restart${plain} - Restart │"
echo -e "${blue}x-ui status${plain} - Current Status │"
echo -e "${blue}x-ui settings${plain} - Current Settings │"
echo -e "${blue}x-ui enable${plain} - Enable Autostart on OS Startup │"
echo -e "${blue}x-ui disable${plain} - Disable Autostart on OS Startup │"
echo -e "${blue}x-ui log${plain} - Check logs │"
echo -e "${blue}x-ui banlog${plain} - Check Fail2ban ban logs │"
echo -e "${blue}x-ui update${plain} - Update │"
echo -e "${blue}x-ui legacy${plain} - legacy version │"
echo -e "${blue}x-ui install${plain} - Install │"
echo -e "${blue}x-ui uninstall${plain} - Uninstall │"
echo -e "${blue}x-ui database${plain} - Database Management │"
echo -e "└───────────────────────────────────────────────────────┘"
}
show_menu() {
echo -e "
╔────────────────────────────────────────────────╗
${green}3X-UI Panel Management Script${plain}
${green}0.${plain} Exit Script │
│────────────────────────────────────────────────│
${green}1.${plain} Install │
${green}2.${plain} Update │
${green}3.${plain} Update Menu │
${green}4.${plain} Legacy Version │
${green}5.${plain} Uninstall │
│────────────────────────────────────────────────│
${green}6.${plain} Reset Username & Password │
${green}7.${plain} Reset Web Base Path │
${green}8.${plain} Reset Settings │
${green}9.${plain} Change Port │
${green}10.${plain} View Current Settings │
│────────────────────────────────────────────────│
${green}11.${plain} Start │
${green}12.${plain} Stop │
${green}13.${plain} Restart │
${green}14.${plain} Check Status │
${green}15.${plain} Logs Management │
│────────────────────────────────────────────────│
${green}16.${plain} Enable Autostart │
${green}17.${plain} Disable Autostart │
│────────────────────────────────────────────────│
${green}18.${plain} SSL Certificate Management │
${green}19.${plain} Cloudflare SSL Certificate │
${green}20.${plain} IP Limit Management │
${green}21.${plain} Firewall Management │
${green}22.${plain} SSH Port Forwarding Management │
│────────────────────────────────────────────────│
${green}23.${plain} Enable BBR │
${green}24.${plain} Update Geo Files │
${green}25.${plain} Speedtest by Ookla │
╚────────────────────────────────────────────────╝
"
echo -e "╔────────────────────────────────────────────────╗"
echo -e "${green}3X-UI Panel Management Script${plain}"
echo -e "${green}0.${plain} Exit Script │"
echo -e "│────────────────────────────────────────────────│"
echo -e "${green}1.${plain} Install │"
echo -e "${green}2.${plain} Update │"
echo -e "${green}3.${plain} Update Menu │"
echo -e "${green}4.${plain} Legacy Version │"
echo -e "${green}5.${plain} Uninstall │"
echo -e "│────────────────────────────────────────────────│"
echo -e "${green}6.${plain} Reset Username & Password │"
echo -e "${green}7.${plain} Reset Web Base Path │"
echo -e "${green}8.${plain} Reset Settings │"
echo -e "${green}9.${plain} Change Port │"
echo -e "${green}10.${plain} View Current Settings │"
echo -e "│────────────────────────────────────────────────│"
echo -e "${green}11.${plain} Start │"
echo -e "${green}12.${plain} Stop │"
echo -e "${green}13.${plain} Restart │"
echo -e "${green}14.${plain} Check Status │"
echo -e "${green}15.${plain} Logs Management │"
echo -e "│────────────────────────────────────────────────│"
echo -e "${green}16.${plain} Enable Autostart │"
echo -e "${green}17.${plain} Disable Autostart │"
echo -e "│────────────────────────────────────────────────│"
echo -e "${green}18.${plain} SSL Certificate Management │"
echo -e "${green}19.${plain} Cloudflare SSL Certificate │"
echo -e "${green}20.${plain} IP Limit Management │"
echo -e "${green}21.${plain} Firewall Management │"
echo -e "${green}22.${plain} SSH Port Forwarding Management │"
echo -e "│────────────────────────────────────────────────│"
echo -e "${green}23.${plain} Enable BBR │"
echo -e "${green}24.${plain} Update Geo Files │"
echo -e "${green}25.${plain} Speedtest by Ookla │"
echo -e "${green}26.${plain} Database Management │"
echo -e "╚────────────────────────────────────────────────╝"
show_status
echo && read -rp "Please enter your selection [0-25]: " num
echo && read -rp "Please enter your selection [0-26]: " num
case "${num}" in
0)
@ -1838,8 +2151,11 @@ show_menu() {
25)
run_speedtest
;;
26)
database_menu
;;
*)
LOGE "Please enter the correct number [0-25]"
LOGE "Please enter the correct number [0-26]"
;;
esac
}
@ -1885,7 +2201,12 @@ if [[ $# > 0 ]]; then
"uninstall")
check_install 0 && uninstall 0
;;
*) show_usage ;;
"database")
check_install 0 && database_menu 0
;;
*)
show_usage
;;
esac
else
show_menu