mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-09-12 13:10:05 +00:00
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:
parent
5dae785786
commit
2fbd1d860d
25 changed files with 1969 additions and 108 deletions
|
@ -1,7 +1,61 @@
|
||||||
#!/bin/sh
|
#!/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
|
# Start fail2ban
|
||||||
[ $XUI_ENABLE_FAIL2BAN == "true" ] && fail2ban-client -x start
|
[ "$XUI_ENABLE_FAIL2BAN" = "true" ] && fail2ban-client -x start
|
||||||
|
|
||||||
# Run x-ui
|
# Run x-ui
|
||||||
exec /app/x-ui
|
exec /app/x-ui
|
||||||
|
|
17
Dockerfile
17
Dockerfile
|
@ -29,12 +29,14 @@ RUN apk add --no-cache --update \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
tzdata \
|
tzdata \
|
||||||
fail2ban \
|
fail2ban \
|
||||||
bash
|
bash \
|
||||||
|
postgresql-client \
|
||||||
|
curl
|
||||||
|
|
||||||
COPY --from=builder /app/build/ /app/
|
COPY --from=builder /app/build/ /app/
|
||||||
COPY --from=builder /app/DockerEntrypoint.sh /app/
|
COPY --from=builder /app/DockerEntrypoint.sh /app/
|
||||||
COPY --from=builder /app/x-ui.sh /usr/bin/x-ui
|
COPY --from=builder /app/x-ui.sh /usr/bin/x-ui
|
||||||
|
COPY wait-for-postgres.sh /app/
|
||||||
|
|
||||||
# Configure fail2ban
|
# Configure fail2ban
|
||||||
RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \
|
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 \
|
RUN chmod +x \
|
||||||
/app/DockerEntrypoint.sh \
|
/app/DockerEntrypoint.sh \
|
||||||
/app/x-ui \
|
/app/x-ui \
|
||||||
|
/app/wait-for-postgres.sh \
|
||||||
/usr/bin/x-ui
|
/usr/bin/x-ui
|
||||||
|
|
||||||
|
# Environment variables for database configuration
|
||||||
ENV XUI_ENABLE_FAIL2BAN="true"
|
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" ]
|
VOLUME [ "/etc/x-ui" ]
|
||||||
CMD [ "./x-ui" ]
|
CMD [ "./x-ui" ]
|
||||||
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]
|
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]
|
||||||
|
|
347
README.Docker.md
Normal file
347
README.Docker.md
Normal 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
130
config/database.go
Normal 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
|
||||||
|
}
|
167
database/db.go
167
database/db.go
|
@ -1,19 +1,22 @@
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"slices"
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/util/crypto"
|
"x-ui/util/crypto"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
|
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/logger"
|
"gorm.io/gorm/logger"
|
||||||
|
@ -113,15 +116,109 @@ func isTableEmpty(tableName string) (bool, error) {
|
||||||
return count == 0, err
|
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 {
|
func InitDB(dbPath string) error {
|
||||||
dir := path.Dir(dbPath)
|
// Try to get configuration from environment file first
|
||||||
err := os.MkdirAll(dir, fs.ModePerm)
|
dbConfig, err := getDatabaseConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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() {
|
if config.IsDebug() {
|
||||||
gormLogger = logger.Default
|
gormLogger = logger.Default
|
||||||
} else {
|
} else {
|
||||||
|
@ -131,7 +228,18 @@ func InitDB(dbPath string) error {
|
||||||
c := &gorm.Config{
|
c := &gorm.Config{
|
||||||
Logger: gormLogger,
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -141,6 +249,9 @@ func InitDB(dbPath string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
isUsersEmpty, err := isTableEmpty("users")
|
isUsersEmpty, err := isTableEmpty("users")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := initUser(); err != nil {
|
if err := initUser(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -148,6 +259,50 @@ func InitDB(dbPath string) error {
|
||||||
return runSeeders(isUsersEmpty)
|
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 {
|
func CloseDB() error {
|
||||||
if db != nil {
|
if db != nil {
|
||||||
sqlDB, err := db.DB()
|
sqlDB, err := db.DB()
|
||||||
|
|
122
docker-compose.postgresql.yml
Normal file
122
docker-compose.postgresql.yml
Normal 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
|
|
@ -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:
|
services:
|
||||||
3x-ui:
|
3x-ui:
|
||||||
image: ghcr.io/mhsanaei/3x-ui:latest
|
image: ghcr.io/mhsanaei/3x-ui:latest
|
||||||
container_name: 3x-ui
|
container_name: 3x-ui
|
||||||
hostname: yourhostname
|
hostname: ${HOSTNAME:-3x-ui}
|
||||||
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
|
|
||||||
restart: unless-stopped
|
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
67
env.example
Normal 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
5
go.mod
|
@ -22,6 +22,7 @@ require (
|
||||||
golang.org/x/crypto v0.38.0
|
golang.org/x/crypto v0.38.0
|
||||||
golang.org/x/text v0.25.0
|
golang.org/x/text v0.25.0
|
||||||
google.golang.org/grpc v1.72.1
|
google.golang.org/grpc v1.72.1
|
||||||
|
gorm.io/driver/postgres v1.5.9
|
||||||
gorm.io/driver/sqlite v1.5.7
|
gorm.io/driver/sqlite v1.5.7
|
||||||
gorm.io/gorm v1.25.12
|
gorm.io/gorm v1.25.12
|
||||||
)
|
)
|
||||||
|
@ -49,6 +50,10 @@ require (
|
||||||
github.com/gorilla/sessions v1.4.0 // indirect
|
github.com/gorilla/sessions v1.4.0 // indirect
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/grbit/go-json v0.11.0 // 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/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
|
10
go.sum
10
go.sum
|
@ -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/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 h1:bAbyMdYrYl/OjYsSqLH99N2DyQ291mHy726Mx+sYrnc=
|
||||||
github.com/grbit/go-json v0.11.0/go.mod h1:IYpHsdybQ386+6g3VE6AXQ3uTGa5mquBme5/ZWmtzek=
|
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 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
||||||
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||||
|
|
46
init-postgres.sql
Normal file
46
init-postgres.sql
Normal 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 $$;
|
180
install.sh
180
install.sh
|
@ -81,6 +81,145 @@ gen_random_string() {
|
||||||
echo "$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() {
|
config_after_install() {
|
||||||
local existing_hasDefaultCredential=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'hasDefaultCredential: .+' | awk '{print $2}')
|
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}')
|
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
|
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/local/x-ui/x-ui.sh
|
||||||
chmod +x /usr/bin/x-ui
|
chmod +x /usr/bin/x-ui
|
||||||
|
|
||||||
|
# Setup database before configuration
|
||||||
|
database_setup
|
||||||
|
|
||||||
config_after_install
|
config_after_install
|
||||||
|
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
|
@ -200,24 +343,25 @@ install_x-ui() {
|
||||||
systemctl start x-ui
|
systemctl start x-ui
|
||||||
echo -e "${green}x-ui ${tag_version}${plain} installation finished, it is running now..."
|
echo -e "${green}x-ui ${tag_version}${plain} installation finished, it is running now..."
|
||||||
echo -e ""
|
echo -e ""
|
||||||
echo -e "┌───────────────────────────────────────────────────────┐
|
echo -e "┌───────────────────────────────────────────────────────┐"
|
||||||
│ ${blue}x-ui control menu usages (subcommands):${plain} │
|
echo -e "│ ${blue}x-ui control menu usages (subcommands):${plain} │"
|
||||||
│ │
|
echo -e "│ │"
|
||||||
│ ${blue}x-ui${plain} - Admin Management Script │
|
echo -e "│ ${blue}x-ui${plain} - Admin Management Script │"
|
||||||
│ ${blue}x-ui start${plain} - Start │
|
echo -e "│ ${blue}x-ui start${plain} - Start │"
|
||||||
│ ${blue}x-ui stop${plain} - Stop │
|
echo -e "│ ${blue}x-ui stop${plain} - Stop │"
|
||||||
│ ${blue}x-ui restart${plain} - Restart │
|
echo -e "│ ${blue}x-ui restart${plain} - Restart │"
|
||||||
│ ${blue}x-ui status${plain} - Current Status │
|
echo -e "│ ${blue}x-ui status${plain} - Current Status │"
|
||||||
│ ${blue}x-ui settings${plain} - Current Settings │
|
echo -e "│ ${blue}x-ui settings${plain} - Current Settings │"
|
||||||
│ ${blue}x-ui enable${plain} - Enable Autostart on OS Startup │
|
echo -e "│ ${blue}x-ui enable${plain} - Enable Autostart on OS Startup │"
|
||||||
│ ${blue}x-ui disable${plain} - Disable Autostart on OS Startup │
|
echo -e "│ ${blue}x-ui disable${plain} - Disable Autostart on OS Startup │"
|
||||||
│ ${blue}x-ui log${plain} - Check logs │
|
echo -e "│ ${blue}x-ui log${plain} - Check logs │"
|
||||||
│ ${blue}x-ui banlog${plain} - Check Fail2ban ban logs │
|
echo -e "│ ${blue}x-ui banlog${plain} - Check Fail2ban ban logs │"
|
||||||
│ ${blue}x-ui update${plain} - Update │
|
echo -e "│ ${blue}x-ui update${plain} - Update │"
|
||||||
│ ${blue}x-ui legacy${plain} - legacy version │
|
echo -e "│ ${blue}x-ui legacy${plain} - legacy version │"
|
||||||
│ ${blue}x-ui install${plain} - Install │
|
echo -e "│ ${blue}x-ui install${plain} - Install │"
|
||||||
│ ${blue}x-ui uninstall${plain} - Uninstall │
|
echo -e "│ ${blue}x-ui uninstall${plain} - Uninstall │"
|
||||||
└───────────────────────────────────────────────────────┘"
|
echo -e "│ ${blue}x-ui database${plain} - Database Management │"
|
||||||
|
echo -e "└───────────────────────────────────────────────────────┘"
|
||||||
}
|
}
|
||||||
|
|
||||||
echo -e "${green}Running...${plain}"
|
echo -e "${green}Running...${plain}"
|
||||||
|
|
23
pgadmin_servers.json
Normal file
23
pgadmin_servers.json
Normal 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
17
wait-for-postgres.sh
Normal 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
|
|
@ -46,7 +46,14 @@ class AllSetting {
|
||||||
this.subJsonNoises = "";
|
this.subJsonNoises = "";
|
||||||
this.subJsonMux = "";
|
this.subJsonMux = "";
|
||||||
this.subJsonRules = "";
|
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";
|
this.timeLocation = "Local";
|
||||||
|
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"x-ui/database"
|
||||||
"x-ui/util/crypto"
|
"x-ui/util/crypto"
|
||||||
"x-ui/web/entity"
|
"x-ui/web/entity"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
|
@ -40,6 +41,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
||||||
g.POST("/updateUser", a.updateUser)
|
g.POST("/updateUser", a.updateUser)
|
||||||
g.POST("/restartPanel", a.restartPanel)
|
g.POST("/restartPanel", a.restartPanel)
|
||||||
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
||||||
|
g.POST("/testDatabaseConnection", a.testDatabaseConnection)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SettingController) getAllSetting(c *gin.Context) {
|
func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||||
|
@ -109,3 +111,19 @@ func (a *SettingController) getDefaultXrayConfig(c *gin.Context) {
|
||||||
}
|
}
|
||||||
jsonObj(c, defaultJsonConfig, nil)
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -61,6 +61,14 @@ type AllSetting struct {
|
||||||
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"`
|
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"`
|
||||||
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
|
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
|
||||||
Datepicker string `json:"datepicker" form:"datepicker"`
|
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 {
|
func (s *AllSetting) CheckValid() error {
|
||||||
|
|
|
@ -115,6 +115,9 @@
|
||||||
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable" :style="{ paddingTop: '20px' }">
|
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable" :style="{ paddingTop: '20px' }">
|
||||||
{{ template "settings/panel/subscription/json" . }}
|
{{ template "settings/panel/subscription/json" . }}
|
||||||
</a-tab-pane>
|
</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-tabs>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
|
|
86
web/html/settings/panel/database.html
Normal file
86
web/html/settings/panel/database.html
Normal 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}}
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"x-ui/config"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
@ -72,6 +73,14 @@ var defaultValueMap = map[string]string{
|
||||||
"warp": "",
|
"warp": "",
|
||||||
"externalTrafficInformEnable": "false",
|
"externalTrafficInformEnable": "false",
|
||||||
"externalTrafficInformURI": "",
|
"externalTrafficInformURI": "",
|
||||||
|
"dbType": "sqlite",
|
||||||
|
"dbHost": "localhost",
|
||||||
|
"dbPort": "5432",
|
||||||
|
"dbName": "x_ui",
|
||||||
|
"dbUser": "x_ui",
|
||||||
|
"dbPassword": "",
|
||||||
|
"dbSSLMode": "disable",
|
||||||
|
"dbTimeZone": "UTC",
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingService struct{}
|
type SettingService struct{}
|
||||||
|
@ -519,6 +528,131 @@ func (s *SettingService) SetExternalTrafficInformURI(InformURI string) error {
|
||||||
return s.setString("externalTrafficInformURI", InformURI)
|
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) {
|
func (s *SettingService) GetIpLimitEnable() (bool, error) {
|
||||||
accessLogPath, err := xray.GetAccessLogPath()
|
accessLogPath, err := xray.GetAccessLogPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -293,11 +293,11 @@
|
||||||
"panelPort" = "Listen Port"
|
"panelPort" = "Listen Port"
|
||||||
"panelPortDesc" = "The port number for the web panel. (must be an unused port)"
|
"panelPortDesc" = "The port number for the web panel. (must be an unused port)"
|
||||||
"publicKeyPath" = "Public Key Path"
|
"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"
|
"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"
|
"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"
|
"pageSize" = "Pagination Size"
|
||||||
"pageSizeDesc" = "Define page size for inbounds table. (0 = disable)"
|
"pageSizeDesc" = "Define page size for inbounds table. (0 = disable)"
|
||||||
"remarkModel" = "Remark Model & Separation Character"
|
"remarkModel" = "Remark Model & Separation Character"
|
||||||
|
@ -345,11 +345,11 @@
|
||||||
"subPort" = "Listen Port"
|
"subPort" = "Listen Port"
|
||||||
"subPortDesc" = "The port number for the subscription service. (must be an unused port)"
|
"subPortDesc" = "The port number for the subscription service. (must be an unused port)"
|
||||||
"subCertPath" = "Public Key Path"
|
"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"
|
"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"
|
"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"
|
"subDomain" = "Listen Domain"
|
||||||
"subDomainDesc" = "The domain name for the subscription service. (leave blank to listen on all domains and IPs)"
|
"subDomainDesc" = "The domain name for the subscription service. (leave blank to listen on all domains and IPs)"
|
||||||
"subUpdates" = "Update Intervals"
|
"subUpdates" = "Update Intervals"
|
||||||
|
@ -551,6 +551,28 @@
|
||||||
"userPassMustBeNotEmpty" = "The new username and password is empty"
|
"userPassMustBeNotEmpty" = "The new username and password is empty"
|
||||||
"getOutboundTrafficError" = "Error getting traffics"
|
"getOutboundTrafficError" = "Error getting traffics"
|
||||||
"resetOutboundTrafficError" = "Error in reset outbound 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]
|
[tgbot]
|
||||||
"keyboardClosed" = "❌ Custom keyboard closed!"
|
"keyboardClosed" = "❌ Custom keyboard closed!"
|
||||||
|
|
|
@ -554,6 +554,28 @@
|
||||||
"userPassMustBeNotEmpty" = "El nuevo nombre de usuario y la nueva contraseña no pueden estar vacíos"
|
"userPassMustBeNotEmpty" = "El nuevo nombre de usuario y la nueva contraseña no pueden estar vacíos"
|
||||||
"getOutboundTrafficError" = "Error al obtener el tráfico saliente"
|
"getOutboundTrafficError" = "Error al obtener el tráfico saliente"
|
||||||
"resetOutboundTrafficError" = "Error al reiniciar 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]
|
[tgbot]
|
||||||
"keyboardClosed" = "❌ ¡Teclado personalizado cerrado!"
|
"keyboardClosed" = "❌ ¡Teclado personalizado cerrado!"
|
||||||
|
|
|
@ -218,7 +218,7 @@
|
||||||
"IPLimitlogclear" = "清除日志"
|
"IPLimitlogclear" = "清除日志"
|
||||||
"setDefaultCert" = "从面板设置证书"
|
"setDefaultCert" = "从面板设置证书"
|
||||||
"telegramDesc" = "请提供Telegram聊天ID。(在机器人中使用'/id'命令)或(@userinfobot"
|
"telegramDesc" = "请提供Telegram聊天ID。(在机器人中使用'/id'命令)或(@userinfobot"
|
||||||
"subscriptionDesc" = "要找到你的订阅 URL,请导航到“详细信息”。此外,你可以为多个客户端使用相同的名称。"
|
"subscriptionDesc" = "要找到你的订阅 URL,请导航到"详细信息"。此外,你可以为多个客户端使用相同的名称。"
|
||||||
"info" = "信息"
|
"info" = "信息"
|
||||||
"same" = "相同"
|
"same" = "相同"
|
||||||
"inboundData" = "入站数据"
|
"inboundData" = "入站数据"
|
||||||
|
@ -554,6 +554,28 @@
|
||||||
"userPassMustBeNotEmpty" = "新用户名和新密码不能为空"
|
"userPassMustBeNotEmpty" = "新用户名和新密码不能为空"
|
||||||
"getOutboundTrafficError" = "获取出站流量错误"
|
"getOutboundTrafficError" = "获取出站流量错误"
|
||||||
"resetOutboundTrafficError" = "重置出站流量错误"
|
"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]
|
[tgbot]
|
||||||
"keyboardClosed" = "❌ 自定义键盘已关闭!"
|
"keyboardClosed" = "❌ 自定义键盘已关闭!"
|
||||||
|
|
42
x-ui-postgresql.service
Normal file
42
x-ui-postgresql.service
Normal 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
437
x-ui.sh
|
@ -1620,6 +1620,319 @@ remove_iplimit() {
|
||||||
esac
|
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() {
|
SSH_port_forwarding() {
|
||||||
local server_ip=$(curl -s https://api.ipify.org)
|
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}')
|
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() {
|
show_usage() {
|
||||||
echo -e "┌───────────────────────────────────────────────────────┐
|
echo -e "┌───────────────────────────────────────────────────────┐"
|
||||||
│ ${blue}x-ui control menu usages (subcommands):${plain} │
|
echo -e "│ ${blue}x-ui control menu usages (subcommands):${plain} │"
|
||||||
│ │
|
echo -e "│ │"
|
||||||
│ ${blue}x-ui${plain} - Admin Management Script │
|
echo -e "│ ${blue}x-ui${plain} - Admin Management Script │"
|
||||||
│ ${blue}x-ui start${plain} - Start │
|
echo -e "│ ${blue}x-ui start${plain} - Start │"
|
||||||
│ ${blue}x-ui stop${plain} - Stop │
|
echo -e "│ ${blue}x-ui stop${plain} - Stop │"
|
||||||
│ ${blue}x-ui restart${plain} - Restart │
|
echo -e "│ ${blue}x-ui restart${plain} - Restart │"
|
||||||
│ ${blue}x-ui status${plain} - Current Status │
|
echo -e "│ ${blue}x-ui status${plain} - Current Status │"
|
||||||
│ ${blue}x-ui settings${plain} - Current Settings │
|
echo -e "│ ${blue}x-ui settings${plain} - Current Settings │"
|
||||||
│ ${blue}x-ui enable${plain} - Enable Autostart on OS Startup │
|
echo -e "│ ${blue}x-ui enable${plain} - Enable Autostart on OS Startup │"
|
||||||
│ ${blue}x-ui disable${plain} - Disable Autostart on OS Startup │
|
echo -e "│ ${blue}x-ui disable${plain} - Disable Autostart on OS Startup │"
|
||||||
│ ${blue}x-ui log${plain} - Check logs │
|
echo -e "│ ${blue}x-ui log${plain} - Check logs │"
|
||||||
│ ${blue}x-ui banlog${plain} - Check Fail2ban ban logs │
|
echo -e "│ ${blue}x-ui banlog${plain} - Check Fail2ban ban logs │"
|
||||||
│ ${blue}x-ui update${plain} - Update │
|
echo -e "│ ${blue}x-ui update${plain} - Update │"
|
||||||
│ ${blue}x-ui legacy${plain} - legacy version │
|
echo -e "│ ${blue}x-ui legacy${plain} - legacy version │"
|
||||||
│ ${blue}x-ui install${plain} - Install │
|
echo -e "│ ${blue}x-ui install${plain} - Install │"
|
||||||
│ ${blue}x-ui uninstall${plain} - Uninstall │
|
echo -e "│ ${blue}x-ui uninstall${plain} - Uninstall │"
|
||||||
└───────────────────────────────────────────────────────┘"
|
echo -e "│ ${blue}x-ui database${plain} - Database Management │"
|
||||||
|
echo -e "└───────────────────────────────────────────────────────┘"
|
||||||
}
|
}
|
||||||
|
|
||||||
show_menu() {
|
show_menu() {
|
||||||
echo -e "
|
echo -e "╔────────────────────────────────────────────────╗"
|
||||||
╔────────────────────────────────────────────────╗
|
echo -e "│ ${green}3X-UI Panel Management Script${plain} │"
|
||||||
│ ${green}3X-UI Panel Management Script${plain} │
|
echo -e "│ ${green}0.${plain} Exit Script │"
|
||||||
│ ${green}0.${plain} Exit Script │
|
echo -e "│────────────────────────────────────────────────│"
|
||||||
│────────────────────────────────────────────────│
|
echo -e "│ ${green}1.${plain} Install │"
|
||||||
│ ${green}1.${plain} Install │
|
echo -e "│ ${green}2.${plain} Update │"
|
||||||
│ ${green}2.${plain} Update │
|
echo -e "│ ${green}3.${plain} Update Menu │"
|
||||||
│ ${green}3.${plain} Update Menu │
|
echo -e "│ ${green}4.${plain} Legacy Version │"
|
||||||
│ ${green}4.${plain} Legacy Version │
|
echo -e "│ ${green}5.${plain} Uninstall │"
|
||||||
│ ${green}5.${plain} Uninstall │
|
echo -e "│────────────────────────────────────────────────│"
|
||||||
│────────────────────────────────────────────────│
|
echo -e "│ ${green}6.${plain} Reset Username & Password │"
|
||||||
│ ${green}6.${plain} Reset Username & Password │
|
echo -e "│ ${green}7.${plain} Reset Web Base Path │"
|
||||||
│ ${green}7.${plain} Reset Web Base Path │
|
echo -e "│ ${green}8.${plain} Reset Settings │"
|
||||||
│ ${green}8.${plain} Reset Settings │
|
echo -e "│ ${green}9.${plain} Change Port │"
|
||||||
│ ${green}9.${plain} Change Port │
|
echo -e "│ ${green}10.${plain} View Current Settings │"
|
||||||
│ ${green}10.${plain} View Current Settings │
|
echo -e "│────────────────────────────────────────────────│"
|
||||||
│────────────────────────────────────────────────│
|
echo -e "│ ${green}11.${plain} Start │"
|
||||||
│ ${green}11.${plain} Start │
|
echo -e "│ ${green}12.${plain} Stop │"
|
||||||
│ ${green}12.${plain} Stop │
|
echo -e "│ ${green}13.${plain} Restart │"
|
||||||
│ ${green}13.${plain} Restart │
|
echo -e "│ ${green}14.${plain} Check Status │"
|
||||||
│ ${green}14.${plain} Check Status │
|
echo -e "│ ${green}15.${plain} Logs Management │"
|
||||||
│ ${green}15.${plain} Logs Management │
|
echo -e "│────────────────────────────────────────────────│"
|
||||||
│────────────────────────────────────────────────│
|
echo -e "│ ${green}16.${plain} Enable Autostart │"
|
||||||
│ ${green}16.${plain} Enable Autostart │
|
echo -e "│ ${green}17.${plain} Disable Autostart │"
|
||||||
│ ${green}17.${plain} Disable Autostart │
|
echo -e "│────────────────────────────────────────────────│"
|
||||||
│────────────────────────────────────────────────│
|
echo -e "│ ${green}18.${plain} SSL Certificate Management │"
|
||||||
│ ${green}18.${plain} SSL Certificate Management │
|
echo -e "│ ${green}19.${plain} Cloudflare SSL Certificate │"
|
||||||
│ ${green}19.${plain} Cloudflare SSL Certificate │
|
echo -e "│ ${green}20.${plain} IP Limit Management │"
|
||||||
│ ${green}20.${plain} IP Limit Management │
|
echo -e "│ ${green}21.${plain} Firewall Management │"
|
||||||
│ ${green}21.${plain} Firewall Management │
|
echo -e "│ ${green}22.${plain} SSH Port Forwarding Management │"
|
||||||
│ ${green}22.${plain} SSH Port Forwarding Management │
|
echo -e "│────────────────────────────────────────────────│"
|
||||||
│────────────────────────────────────────────────│
|
echo -e "│ ${green}23.${plain} Enable BBR │"
|
||||||
│ ${green}23.${plain} Enable BBR │
|
echo -e "│ ${green}24.${plain} Update Geo Files │"
|
||||||
│ ${green}24.${plain} Update Geo Files │
|
echo -e "│ ${green}25.${plain} Speedtest by Ookla │"
|
||||||
│ ${green}25.${plain} Speedtest by Ookla │
|
echo -e "│ ${green}26.${plain} Database Management │"
|
||||||
╚────────────────────────────────────────────────╝
|
echo -e "╚────────────────────────────────────────────────╝"
|
||||||
"
|
|
||||||
show_status
|
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
|
case "${num}" in
|
||||||
0)
|
0)
|
||||||
|
@ -1838,8 +2151,11 @@ show_menu() {
|
||||||
25)
|
25)
|
||||||
run_speedtest
|
run_speedtest
|
||||||
;;
|
;;
|
||||||
|
26)
|
||||||
|
database_menu
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
LOGE "Please enter the correct number [0-25]"
|
LOGE "Please enter the correct number [0-26]"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
@ -1885,7 +2201,12 @@ if [[ $# > 0 ]]; then
|
||||||
"uninstall")
|
"uninstall")
|
||||||
check_install 0 && uninstall 0
|
check_install 0 && uninstall 0
|
||||||
;;
|
;;
|
||||||
*) show_usage ;;
|
"database")
|
||||||
|
check_install 0 && database_menu 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_usage
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
else
|
else
|
||||||
show_menu
|
show_menu
|
||||||
|
|
Loading…
Reference in a new issue