Refactoring and fix
@@ -50,5 +50,4 @@ handlers/texts.py
|
|||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
nginx.conf
|
nginx.conf
|
||||||
scripts/nginx.sh
|
scripts
|
||||||
scripts/
|
|
||||||
@@ -8,10 +8,10 @@ router = Router()
|
|||||||
|
|
||||||
|
|
||||||
@router.message(Command("start"))
|
@router.message(Command("start"))
|
||||||
async def handle_start(message: types.Message, state: FSMContext):
|
async def handle_start(message: types.Message, state: FSMContext, admin: bool):
|
||||||
await start_command(message)
|
await start_command(message, admin)
|
||||||
|
|
||||||
|
|
||||||
@router.message(Command("menu"))
|
@router.message(Command("menu"))
|
||||||
async def handle_menu(message: types.Message, state: FSMContext):
|
async def handle_menu(message: types.Message, state: FSMContext, admin: bool):
|
||||||
await start_command(message)
|
await start_command(message, admin)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ async def send_instructions(callback_query: types.CallbackQuery):
|
|||||||
|
|
||||||
instructions_message = INSTRUCTIONS
|
instructions_message = INSTRUCTIONS
|
||||||
|
|
||||||
image_path = os.path.join(os.path.dirname(__file__), "instructions.jpg")
|
image_path = os.path.join("img", "instructions.jpg")
|
||||||
|
|
||||||
if not os.path.isfile(image_path):
|
if not os.path.isfile(image_path):
|
||||||
await callback_query.message.answer("Файл изображения не найден.")
|
await callback_query.message.answer("Файл изображения не найден.")
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery):
|
|||||||
"<i>👇 Выберите устройство для управления подпиской:</i>"
|
"<i>👇 Выберите устройство для управления подпиской:</i>"
|
||||||
)
|
)
|
||||||
|
|
||||||
image_path = os.path.join(os.path.dirname(__file__), "pic_keys.jpg")
|
image_path = os.path.join("img", "pic_keys.jpg")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await bot.delete_message(
|
await bot.delete_message(
|
||||||
@@ -104,7 +104,7 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||||
|
|
||||||
image_path = os.path.join(os.path.dirname(__file__), "pic_keys.jpg")
|
image_path = os.path.join("img", "pic_keys.jpg")
|
||||||
|
|
||||||
if os.path.isfile(image_path):
|
if os.path.isfile(image_path):
|
||||||
with open(image_path, "rb") as image_file:
|
with open(image_path, "rb") as image_file:
|
||||||
@@ -231,7 +231,7 @@ async def process_callback_view_key(callback_query: types.CallbackQuery):
|
|||||||
|
|
||||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=inline_keyboard)
|
keyboard = types.InlineKeyboardMarkup(inline_keyboard=inline_keyboard)
|
||||||
|
|
||||||
image_path = os.path.join(os.path.dirname(__file__), "pic_view.jpg")
|
image_path = os.path.join("img", "pic_view.jpg")
|
||||||
|
|
||||||
if not os.path.isfile(image_path):
|
if not os.path.isfile(image_path):
|
||||||
await bot.send_message(tg_id, "Файл изображения не найден.")
|
await bot.send_message(tg_id, "Файл изображения не найден.")
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from loguru import logger
|
|||||||
from robokassa import HashAlgorithm, Robokassa
|
from robokassa import HashAlgorithm, Robokassa
|
||||||
|
|
||||||
from bot import bot
|
from bot import bot
|
||||||
from config import ROBOKASSA_LOGIN, ROBOKASSA_PASSWORD1, ROBOKASSA_PASSWORD2, ROBOKASSA_TEST_MODE
|
from config import ROBOKASSA_ENABLE,ROBOKASSA_LOGIN, ROBOKASSA_PASSWORD1, ROBOKASSA_PASSWORD2, ROBOKASSA_TEST_MODE
|
||||||
from database import add_connection, check_connection_exists, get_key_count, update_balance
|
from database import add_connection, check_connection_exists, get_key_count, update_balance
|
||||||
from handlers.texts import PAYMENT_OPTIONS
|
from handlers.texts import PAYMENT_OPTIONS
|
||||||
|
|
||||||
@@ -21,16 +21,16 @@ class ReplenishBalanceState(StatesGroup):
|
|||||||
choosing_amount_robokassa = State()
|
choosing_amount_robokassa = State()
|
||||||
waiting_for_payment_confirmation_robokassa = State()
|
waiting_for_payment_confirmation_robokassa = State()
|
||||||
|
|
||||||
|
if ROBOKASSA_ENABLE:
|
||||||
|
robokassa = Robokassa(
|
||||||
|
merchant_login=ROBOKASSA_LOGIN,
|
||||||
|
password1=ROBOKASSA_PASSWORD1,
|
||||||
|
password2=ROBOKASSA_PASSWORD2,
|
||||||
|
algorithm=HashAlgorithm.md5,
|
||||||
|
is_test=ROBOKASSA_TEST_MODE,
|
||||||
|
)
|
||||||
|
|
||||||
robokassa = Robokassa(
|
logger.info("Robokassa initialized with login: {}", ROBOKASSA_LOGIN)
|
||||||
merchant_login=ROBOKASSA_LOGIN,
|
|
||||||
password1=ROBOKASSA_PASSWORD1,
|
|
||||||
password2=ROBOKASSA_PASSWORD2,
|
|
||||||
algorithm=HashAlgorithm.md5,
|
|
||||||
is_test=ROBOKASSA_TEST_MODE,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info("Robokassa initialized with login: {}", ROBOKASSA_LOGIN)
|
|
||||||
|
|
||||||
|
|
||||||
def generate_payment_link(amount, inv_id, description):
|
def generate_payment_link(amount, inv_id, description):
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ async def process_callback_view_profile(
|
|||||||
chat_id = callback_query.from_user.id
|
chat_id = callback_query.from_user.id
|
||||||
username = callback_query.from_user.full_name
|
username = callback_query.from_user.full_name
|
||||||
|
|
||||||
image_path = os.path.join(os.path.dirname(__file__), "pic.jpg")
|
image_path = os.path.join("img", "pic.jpg")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
key_count = await get_key_count(chat_id)
|
key_count = await get_key_count(chat_id)
|
||||||
@@ -99,7 +99,7 @@ async def invite_handler(callback_query: types.CallbackQuery):
|
|||||||
|
|
||||||
invite_message = invite_message_send(referral_link, referral_stats)
|
invite_message = invite_message_send(referral_link, referral_stats)
|
||||||
|
|
||||||
image_path = os.path.join(os.path.dirname(__file__), "pic_invite.jpg")
|
image_path = os.path.join("img", "pic_invite.jpg")
|
||||||
|
|
||||||
builder = InlineKeyboardBuilder()
|
builder = InlineKeyboardBuilder()
|
||||||
builder.row(
|
builder.row(
|
||||||
@@ -142,6 +142,6 @@ async def invite_handler(callback_query: types.CallbackQuery):
|
|||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data == "view_profile")
|
@router.callback_query(F.data == "view_profile")
|
||||||
async def view_profile_handler(callback_query: types.CallbackQuery, state: FSMContext):
|
async def view_profile_handler(callback_query: types.CallbackQuery, state: FSMContext,admin:bool):
|
||||||
await state.clear()
|
await state.clear()
|
||||||
await process_callback_view_profile(callback_query, state)
|
await process_callback_view_profile(callback_query, state,admin)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ router = Router()
|
|||||||
|
|
||||||
|
|
||||||
async def send_welcome_message(chat_id: int, trial_status: int, admin: bool):
|
async def send_welcome_message(chat_id: int, trial_status: int, admin: bool):
|
||||||
image_path = os.path.join(os.path.dirname(__file__), "pic.jpg")
|
image_path = os.path.join("img", "pic.jpg")
|
||||||
|
|
||||||
builder = InlineKeyboardBuilder()
|
builder = InlineKeyboardBuilder()
|
||||||
if trial_status == 0:
|
if trial_status == 0:
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
@@ -1,164 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Цвета для вывода
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
echo -e "${BLUE}3x-ui Installation Script by izzzzzi${NC}"
|
|
||||||
echo "----------------------------------------"
|
|
||||||
|
|
||||||
# Запрос данных у пользователя
|
|
||||||
read -p "Enter your domain (e.g., example.com): " DOMAIN
|
|
||||||
read -p "Enter desired admin panel port (default: 2053): " PANEL_PORT
|
|
||||||
read -p "Enter desired sub panel port (default: 2054): " SUB_PANEL_PORT
|
|
||||||
|
|
||||||
# Использование значения по умолчанию для порта, если не указано
|
|
||||||
PANEL_PORT=${PANEL_PORT:-2053}
|
|
||||||
SUB_PANEL_PORT=${SUB_PANEL_PORT:-2054}
|
|
||||||
|
|
||||||
# Создание необходимых директорий
|
|
||||||
echo -e "${GREEN}Creating directories...${NC}"
|
|
||||||
mkdir -p nginx/conf.d db cert
|
|
||||||
|
|
||||||
# Установка certbot
|
|
||||||
echo -e "${GREEN}Installing certbot...${NC}"
|
|
||||||
apt-get update
|
|
||||||
apt-get install certbot -y
|
|
||||||
|
|
||||||
# Остановка nginx если он запущен
|
|
||||||
echo -e "${GREEN}Stopping nginx if running...${NC}"
|
|
||||||
docker-compose down 2>/dev/null
|
|
||||||
systemctl stop nginx 2>/dev/null
|
|
||||||
|
|
||||||
# Получение сертификата
|
|
||||||
echo -e "${GREEN}Obtaining SSL certificate...${NC}"
|
|
||||||
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d $DOMAIN
|
|
||||||
|
|
||||||
# Проверка успешности получения сертификата
|
|
||||||
if [ ! -d "/etc/letsencrypt/live/$DOMAIN" ]; then
|
|
||||||
echo "Failed to obtain SSL certificate. Please check your domain settings."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Тестирование автообновления
|
|
||||||
echo -e "${GREEN}Testing certificate renewal...${NC}"
|
|
||||||
certbot renew --dry-run
|
|
||||||
|
|
||||||
# Создание docker-compose.yml
|
|
||||||
echo -e "${GREEN}Creating docker-compose.yml...${NC}"
|
|
||||||
cat > docker-compose.yml << EOL
|
|
||||||
version: "3"
|
|
||||||
|
|
||||||
services:
|
|
||||||
nginx:
|
|
||||||
image: nginx:alpine
|
|
||||||
container_name: nginx
|
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
- "443:443"
|
|
||||||
volumes:
|
|
||||||
- ./nginx/conf.d:/etc/nginx/conf.d
|
|
||||||
- /etc/letsencrypt:/etc/letsencrypt
|
|
||||||
- /var/lib/letsencrypt:/var/lib/letsencrypt
|
|
||||||
networks:
|
|
||||||
- proxy-network
|
|
||||||
restart: unless-stopped
|
|
||||||
depends_on:
|
|
||||||
- 3x-ui
|
|
||||||
|
|
||||||
3x-ui:
|
|
||||||
image: ghcr.io/mhsanaei/3x-ui:latest
|
|
||||||
container_name: 3x-ui
|
|
||||||
hostname: ${DOMAIN}
|
|
||||||
volumes:
|
|
||||||
- \$PWD/db/:/etc/x-ui/
|
|
||||||
- \$PWD/cert/:/root/cert/
|
|
||||||
- /etc/letsencrypt:/etc/letsencrypt:ro
|
|
||||||
environment:
|
|
||||||
XRAY_VMESS_AEAD_FORCED: "false"
|
|
||||||
tty: true
|
|
||||||
networks:
|
|
||||||
- proxy-network
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
networks:
|
|
||||||
proxy-network:
|
|
||||||
driver: bridge
|
|
||||||
EOL
|
|
||||||
|
|
||||||
# Создание конфигурации Nginx
|
|
||||||
echo -e "${GREEN}Creating Nginx configuration...${NC}"
|
|
||||||
cat > nginx/conf.d/default.conf << EOL
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name ${DOMAIN};
|
|
||||||
|
|
||||||
location / {
|
|
||||||
return 301 https://\$host\$request_uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl;
|
|
||||||
server_name ${DOMAIN};
|
|
||||||
|
|
||||||
ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem;
|
|
||||||
ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem;
|
|
||||||
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_prefer_server_ciphers off;
|
|
||||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
||||||
proxy_set_header Host \$http_host;
|
|
||||||
proxy_set_header X-Real-IP \$remote_addr;
|
|
||||||
proxy_set_header Range \$http_range;
|
|
||||||
proxy_set_header If-Range \$http_if_range;
|
|
||||||
proxy_redirect off;
|
|
||||||
proxy_pass http://3x-ui:${PANEL_PORT};
|
|
||||||
}
|
|
||||||
|
|
||||||
location /sub {
|
|
||||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
||||||
proxy_set_header Host \$http_host;
|
|
||||||
proxy_set_header X-Real-IP \$remote_addr;
|
|
||||||
proxy_set_header Range \$http_range;
|
|
||||||
proxy_set_header If-Range \$http_if_range;
|
|
||||||
proxy_redirect off;
|
|
||||||
proxy_pass http://3x-ui:${SUB_PANEL_PORT};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOL
|
|
||||||
|
|
||||||
# Проверка наличия Docker и Docker Compose
|
|
||||||
if ! command -v docker &> /dev/null || ! command -v docker-compose &> /dev/null; then
|
|
||||||
echo -e "${GREEN}Installing Docker and Docker Compose...${NC}"
|
|
||||||
curl -fsSL https://get.docker.com | sh
|
|
||||||
curl -L "https://github.com/docker/compose/releases/download/v2.12.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
|
||||||
chmod +x /usr/local/bin/docker-compose
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Настройка автообновления сертификатов
|
|
||||||
echo -e "${GREEN}Setting up certificate auto-renewal...${NC}"
|
|
||||||
cat > /etc/cron.d/certbot-renew << EOL
|
|
||||||
0 */12 * * * root certbot renew --quiet --deploy-hook "docker-compose -f $(pwd)/docker-compose.yml restart nginx"
|
|
||||||
EOL
|
|
||||||
|
|
||||||
# Запуск сервисов
|
|
||||||
echo -e "${GREEN}Starting all services...${NC}"
|
|
||||||
docker-compose up -d
|
|
||||||
|
|
||||||
echo -e "${GREEN}Installation completed!${NC}"
|
|
||||||
echo -e "${BLUE}You can access your 3x-ui panel at: https://${DOMAIN}${NC}"
|
|
||||||
echo -e "${YELLOW}Important: Default access to 3x-ui panel - ${NC}"
|
|
||||||
echo -e "${YELLOW}Login: admin${NC}"
|
|
||||||
echo -e "${YELLOW}Password: admin${NC}"
|
|
||||||
echo -e "${YELLOW}For security, it is recommended to change the password at: https://${DOMAIN}/panel/settings${NC}"
|
|
||||||
echo "Please wait a few minutes for all services to start properly."
|
|
||||||
echo "Default credentials can be found in the 3x-ui documentation."
|
|
||||||
|
|
||||||
EOL
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Цвета для вывода
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
RED='\033[0;31m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
echo -e "${BLUE}PostgreSQL Installation and Setup Script by izzzzzi${NC}"
|
|
||||||
echo "----------------------------------------"
|
|
||||||
|
|
||||||
# Запрос данных у пользователя
|
|
||||||
read -p "Enter PostgreSQL port (default: 5432): " DB_PORT
|
|
||||||
read -p "Enter database name: " DB_NAME
|
|
||||||
read -p "Enter database user: " DB_USER
|
|
||||||
read -s -p "Enter database password: " DB_PASS
|
|
||||||
echo
|
|
||||||
read -s -p "Confirm database password: " DB_PASS_CONFIRM
|
|
||||||
echo
|
|
||||||
|
|
||||||
# Проверка паролей
|
|
||||||
if [ "$DB_PASS" != "$DB_PASS_CONFIRM" ]; then
|
|
||||||
echo -e "${RED}Passwords do not match!${NC}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Использование значения по умолчанию для порта
|
|
||||||
DB_PORT=${DB_PORT:-5432}
|
|
||||||
|
|
||||||
# Установка PostgreSQL
|
|
||||||
echo -e "${GREEN}Installing PostgreSQL...${NC}"
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y postgresql postgresql-contrib
|
|
||||||
|
|
||||||
# Остановка PostgreSQL для изменения конфигурации
|
|
||||||
systemctl stop postgresql
|
|
||||||
|
|
||||||
# Настройка PostgreSQL для внешних подключений
|
|
||||||
echo -e "${GREEN}Configuring PostgreSQL...${NC}"
|
|
||||||
|
|
||||||
# Настройка postgresql.conf
|
|
||||||
PG_VERSION=$(ls /etc/postgresql/)
|
|
||||||
PG_CONF="/etc/postgresql/$PG_VERSION/main/postgresql.conf"
|
|
||||||
PG_HBA="/etc/postgresql/$PG_VERSION/main/pg_hba.conf"
|
|
||||||
|
|
||||||
# Backup конфигурационных файлов
|
|
||||||
cp $PG_CONF "${PG_CONF}.backup"
|
|
||||||
cp $PG_HBA "${PG_HBA}.backup"
|
|
||||||
|
|
||||||
# Изменение postgresql.conf
|
|
||||||
sed -i "s/#listen_addresses = 'localhost'/listen_addresses = '*'/" $PG_CONF
|
|
||||||
sed -i "s/#port = 5432/port = $DB_PORT/" $PG_CONF
|
|
||||||
|
|
||||||
# Изменение pg_hba.conf
|
|
||||||
cat > $PG_HBA << EOL
|
|
||||||
# TYPE DATABASE USER ADDRESS METHOD
|
|
||||||
local all postgres peer
|
|
||||||
local all all peer
|
|
||||||
host all all 127.0.0.1/32 md5
|
|
||||||
host all all ::1/128 md5
|
|
||||||
host all all 0.0.0.0/0 md5
|
|
||||||
EOL
|
|
||||||
|
|
||||||
# Запуск PostgreSQL
|
|
||||||
systemctl start postgresql
|
|
||||||
systemctl enable postgresql
|
|
||||||
|
|
||||||
# Создание пользователя и базы данных
|
|
||||||
echo -e "${GREEN}Creating database and user...${NC}"
|
|
||||||
sudo -u postgres psql << EOF
|
|
||||||
CREATE USER $DB_USER WITH PASSWORD '$DB_PASS';
|
|
||||||
CREATE DATABASE $DB_NAME OWNER $DB_USER;
|
|
||||||
ALTER USER $DB_USER WITH SUPERUSER;
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Настройка файрвола
|
|
||||||
echo -e "${GREEN}Configuring firewall...${NC}"
|
|
||||||
if command -v ufw >/dev/null; then
|
|
||||||
ufw allow $DB_PORT/tcp
|
|
||||||
ufw status
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Проверка статуса PostgreSQL
|
|
||||||
systemctl status postgresql --no-pager
|
|
||||||
|
|
||||||
echo -e "${GREEN}Installation completed!${NC}"
|
|
||||||
echo -e "${BLUE}PostgreSQL is running on port: ${DB_PORT}${NC}"
|
|
||||||
echo -e "${BLUE}Database name: ${DB_NAME}${NC}"
|
|
||||||
echo -e "${BLUE}Database user: ${DB_USER}${NC}"
|
|
||||||
echo "You can now connect to your database using:"
|
|
||||||
echo "psql -h localhost -p $DB_PORT -U $DB_USER -d $DB_NAME"
|
|
||||||
echo
|
|
||||||
echo "For remote connections use:"
|
|
||||||
echo "psql -h YOUR_SERVER_IP -p $DB_PORT -U $DB_USER -d $DB_NAME"
|
|
||||||
echo
|
|
||||||
echo -e "${RED}Important: Make sure to save these credentials in a secure place!${NC}"
|
|
||||||
|
|
||||||
# Проверка подключения
|
|
||||||
echo -e "${GREEN}Testing connection...${NC}"
|
|
||||||
PGPASSWORD=$DB_PASS psql -h localhost -p $DB_PORT -U $DB_USER -d $DB_NAME -c "\conninfo"
|
|
||||||
|
|
||||||
# Добавление информации о создании бэкапов
|
|
||||||
echo
|
|
||||||
echo "To create a backup, use:"
|
|
||||||
echo "pg_dump -h localhost -p $DB_PORT -U $DB_USER -d $DB_NAME > backup.sql"
|
|
||||||
echo
|
|
||||||
echo "To restore from backup, use:"
|
|
||||||
echo "psql -h localhost -p $DB_PORT -U $DB_USER -d $DB_NAME < backup.sql"
|
|
||||||