Add deployment script and auto-download dependencies
- Создан скрипт deploy.sh для автоматического развертывания на новом сервере - Добавлена автоматическая загрузка Firefox ESR и Geckodriver в install.sh - Добавлена поддержка переменных окружения для секретов - Обновлена документация (DEPLOY.md, README.md) - Скрипт deploy.sh поддерживает интерактивный ввод секретов
This commit is contained in:
parent
5824a82857
commit
ec0a1b89c7
173
DEPLOY.md
Normal file
173
DEPLOY.md
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
# Развертывание F7cloud Talk Recording Server
|
||||
|
||||
## Быстрое развертывание на новом сервере
|
||||
|
||||
### Автоматическое развертывание (рекомендуется)
|
||||
|
||||
Самый простой способ - использовать скрипт автоматического развертывания:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://git.f7cloud.ru/root/F7_recording/raw/branch/main/deploy.sh | bash
|
||||
```
|
||||
|
||||
или
|
||||
|
||||
```bash
|
||||
wget -qO- https://git.f7cloud.ru/root/F7_recording/raw/branch/main/deploy.sh | bash
|
||||
```
|
||||
|
||||
Скрипт выполнит:
|
||||
1. Клонирование репозитория
|
||||
2. Интерактивный ввод необходимых параметров (HPB URL, секреты)
|
||||
3. Автоматическую установку всех зависимостей
|
||||
4. Настройку конфигурации
|
||||
5. Создание systemd service
|
||||
|
||||
### Ручное развертывание
|
||||
|
||||
#### 1. Клонирование репозитория
|
||||
|
||||
```bash
|
||||
cd /opt
|
||||
git clone https://git.f7cloud.ru/root/F7_recording.git f7cloud-talk-recording
|
||||
cd f7cloud-talk-recording
|
||||
```
|
||||
|
||||
#### 2. Запуск установки с параметрами
|
||||
|
||||
```bash
|
||||
sudo ./install.sh \
|
||||
--hpb-url https://hpb.example.com \
|
||||
--hpb-secret your-hpb-secret-here \
|
||||
--f7cloud-url https://f7cloud.example.com \
|
||||
--f7cloud-secret your-f7cloud-secret-here
|
||||
```
|
||||
|
||||
#### 3. Запуск сервиса
|
||||
|
||||
```bash
|
||||
sudo systemctl start f7cloud-talk-recording
|
||||
sudo systemctl enable f7cloud-talk-recording
|
||||
```
|
||||
|
||||
## Получение секретов
|
||||
|
||||
### HPB Secret (internalsecret)
|
||||
|
||||
Секрет для подключения к HPB находится в конфигурации signaling сервера:
|
||||
|
||||
```bash
|
||||
# На сервере signaling сервера
|
||||
cat /etc/nextcloud-spreed-signaling/server.conf | grep internalsecret
|
||||
```
|
||||
|
||||
Или в секции `[clients]` файла `/etc/nextcloud-spreed-signaling/server.conf`:
|
||||
|
||||
```ini
|
||||
[clients]
|
||||
internalsecret = your-hpb-secret-here
|
||||
```
|
||||
|
||||
### F7cloud Secret
|
||||
|
||||
Секрет для подключения к F7cloud настраивается в админ-панели F7cloud:
|
||||
|
||||
1. Войдите в F7cloud как администратор
|
||||
2. Перейдите в **Настройки** → **Talk**
|
||||
3. Найдите раздел **Recording backend**
|
||||
4. Скопируйте секрет из поля **Secret**
|
||||
|
||||
## Автоматическая загрузка зависимостей
|
||||
|
||||
Скрипт `install.sh` автоматически загружает зависимости, если они отсутствуют:
|
||||
|
||||
- **Firefox ESR** - скачивается с официального сайта Mozilla
|
||||
- **Geckodriver** - скачивается с GitHub releases
|
||||
|
||||
Зависимости определяются автоматически по архитектуре системы (x86_64 или aarch64).
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
Вы также можете использовать переменные окружения вместо интерактивного ввода:
|
||||
|
||||
```bash
|
||||
export HPB_URL="https://hpb.example.com"
|
||||
export HPB_SECRET="your-hpb-secret"
|
||||
export F7CLOUD_URL="https://f7cloud.example.com"
|
||||
export F7CLOUD_SECRET="your-f7cloud-secret"
|
||||
|
||||
sudo ./install.sh \
|
||||
--hpb-url "$HPB_URL" \
|
||||
--hpb-secret "$HPB_SECRET" \
|
||||
--f7cloud-url "$F7CLOUD_URL" \
|
||||
--f7cloud-secret "$F7CLOUD_SECRET"
|
||||
```
|
||||
|
||||
## Проверка установки
|
||||
|
||||
После установки проверьте:
|
||||
|
||||
```bash
|
||||
# Статус сервиса
|
||||
sudo systemctl status f7cloud-talk-recording
|
||||
|
||||
# Логи
|
||||
sudo journalctl -u f7cloud-talk-recording -f
|
||||
|
||||
# Проверка конфигурации
|
||||
sudo cat /etc/f7cloud-talk-recording/server.conf
|
||||
|
||||
# Проверка Firefox
|
||||
/usr/local/bin/firefox-esr --version
|
||||
|
||||
# Проверка Geckodriver
|
||||
/usr/local/bin/geckodriver --version
|
||||
```
|
||||
|
||||
## Обновление
|
||||
|
||||
Для обновления на существующем сервере:
|
||||
|
||||
```bash
|
||||
cd /opt/f7cloud-talk-recording
|
||||
sudo systemctl stop f7cloud-talk-recording
|
||||
git pull
|
||||
source venv/bin/activate
|
||||
pip install -e .
|
||||
sudo systemctl start f7cloud-talk-recording
|
||||
```
|
||||
|
||||
## Устранение неполадок
|
||||
|
||||
### Проблемы с загрузкой зависимостей
|
||||
|
||||
Если автоматическая загрузка не работает, установите зависимости вручную:
|
||||
|
||||
```bash
|
||||
cd /opt/f7cloud-talk-recording/dependencies
|
||||
|
||||
# Firefox ESR
|
||||
wget https://download.mozilla.org/?product=firefox-esr-latest-ssl&os=linux64&lang=en-US -O firefox-esr.tar.bz2
|
||||
tar -xjf firefox-esr.tar.bz2
|
||||
mv firefox firefox-esr
|
||||
|
||||
# Geckodriver
|
||||
wget https://github.com/mozilla/geckodriver/releases/latest/download/geckodriver-v0.34.0-linux64.tar.gz
|
||||
tar -xzf geckodriver-v0.34.0-linux64.tar.gz
|
||||
chmod +x geckodriver
|
||||
```
|
||||
|
||||
### Проблемы с правами доступа
|
||||
|
||||
Убедитесь, что пользователь `f7cloud-talk-recording` имеет права:
|
||||
|
||||
```bash
|
||||
sudo chown -R f7cloud-talk-recording:f7cloud-talk-recording /var/lib/f7cloud-talk-recording
|
||||
sudo chmod 600 /etc/f7cloud-talk-recording/server.conf
|
||||
```
|
||||
|
||||
## Дополнительная информация
|
||||
|
||||
- Подробная инструкция по установке: `INSTALL.md`
|
||||
- Примеры использования: `INSTALL_EXAMPLE.md`
|
||||
- Структура проекта: `PROJECT_STRUCTURE.md`
|
||||
16
README.md
16
README.md
|
|
@ -41,10 +41,22 @@ f7cloud-talk-recording/
|
|||
|
||||
## Быстрый старт
|
||||
|
||||
### Установка
|
||||
### Автоматическое развертывание на новом сервере
|
||||
|
||||
Самый простой способ - использовать скрипт автоматического развертывания:
|
||||
|
||||
```bash
|
||||
sudo ./install.sh
|
||||
curl -fsSL https://git.f7cloud.ru/root/F7_recording/raw/branch/main/deploy.sh | bash
|
||||
```
|
||||
|
||||
Скрипт выполнит все необходимые шаги, включая интерактивный ввод секретов.
|
||||
|
||||
### Установка из клонированного репозитория
|
||||
|
||||
```bash
|
||||
git clone https://git.f7cloud.ru/root/F7_recording.git
|
||||
cd F7_recording
|
||||
sudo ./install.sh --hpb-url https://hpb.example.com --hpb-secret your-secret-here
|
||||
```
|
||||
|
||||
### Конфигурация
|
||||
|
|
|
|||
202
deploy.sh
Executable file
202
deploy.sh
Executable file
|
|
@ -0,0 +1,202 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Скрипт развертывания F7cloud Talk Recording Server на новом сервере
|
||||
# Этот скрипт клонирует репозиторий и выполняет установку с интерактивным вводом секретов
|
||||
#
|
||||
# Использование:
|
||||
# curl -fsSL https://git.f7cloud.ru/root/F7_recording/raw/branch/main/deploy.sh | bash
|
||||
# или
|
||||
# wget -qO- https://git.f7cloud.ru/root/F7_recording/raw/branch/main/deploy.sh | bash
|
||||
|
||||
set -e
|
||||
|
||||
# Цвета для вывода
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Функция для вывода сообщений
|
||||
info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
question() {
|
||||
echo -e "${BLUE}[?]${NC} $1"
|
||||
}
|
||||
|
||||
# Проверка прав root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
error "Пожалуйста, запустите скрипт с правами root (sudo)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "=========================================="
|
||||
info "Развертывание F7cloud Talk Recording Server"
|
||||
info "=========================================="
|
||||
echo ""
|
||||
|
||||
# Параметры по умолчанию
|
||||
REPO_URL="https://git.f7cloud.ru/root/F7_recording.git"
|
||||
INSTALL_DIR="/opt/f7cloud-talk-recording"
|
||||
HPB_URL=""
|
||||
HPB_SECRET=""
|
||||
F7CLOUD_URL=""
|
||||
F7CLOUD_SECRET=""
|
||||
LISTEN_ADDRESS="127.0.0.1:8000"
|
||||
AUTO_DOWNLOAD_DEPS=true
|
||||
|
||||
# Функция для безопасного ввода секрета
|
||||
read_secret() {
|
||||
local prompt="$1"
|
||||
local var_name="$2"
|
||||
local value
|
||||
|
||||
question "$prompt"
|
||||
read -s value
|
||||
echo ""
|
||||
eval "$var_name='$value'"
|
||||
}
|
||||
|
||||
# Функция для ввода URL
|
||||
read_url() {
|
||||
local prompt="$1"
|
||||
local var_name="$2"
|
||||
local default="$3"
|
||||
local value
|
||||
|
||||
if [ -n "$default" ]; then
|
||||
question "$prompt (по умолчанию: $default)"
|
||||
else
|
||||
question "$prompt"
|
||||
fi
|
||||
read value
|
||||
if [ -z "$value" ] && [ -n "$default" ]; then
|
||||
value="$default"
|
||||
fi
|
||||
eval "$var_name='$value'"
|
||||
}
|
||||
|
||||
# Интерактивный ввод параметров
|
||||
info "Настройка параметров установки..."
|
||||
echo ""
|
||||
|
||||
# HPB URL
|
||||
while [ -z "$HPB_URL" ]; do
|
||||
read_url "Введите URL сервера HPB (signaling server)" HPB_URL
|
||||
if [ -z "$HPB_URL" ]; then
|
||||
error "URL HPB обязателен для ввода!"
|
||||
fi
|
||||
done
|
||||
|
||||
# HPB Secret
|
||||
while [ -z "$HPB_SECRET" ]; do
|
||||
read_secret "Введите секрет для подключения к HPB (internalsecret)" HPB_SECRET
|
||||
if [ -z "$HPB_SECRET" ]; then
|
||||
error "Секрет HPB обязателен для ввода!"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# F7cloud URL (опционально)
|
||||
read_url "Введите URL сервера F7cloud (опционально, можно пропустить)" F7CLOUD_URL ""
|
||||
|
||||
# F7cloud Secret (если указан URL)
|
||||
if [ -n "$F7CLOUD_URL" ]; then
|
||||
while [ -z "$F7CLOUD_SECRET" ]; do
|
||||
read_secret "Введите секрет для подключения к F7cloud" F7CLOUD_SECRET
|
||||
if [ -z "$F7CLOUD_SECRET" ]; then
|
||||
error "Секрет F7cloud обязателен, если указан URL!"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Listen address
|
||||
read_url "Введите адрес и порт для прослушивания" LISTEN_ADDRESS "127.0.0.1:8000"
|
||||
|
||||
echo ""
|
||||
info "Параметры установки:"
|
||||
info " HPB URL: $HPB_URL"
|
||||
info " Listen: $LISTEN_ADDRESS"
|
||||
if [ -n "$F7CLOUD_URL" ]; then
|
||||
info " F7cloud URL: $F7CLOUD_URL"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Подтверждение
|
||||
question "Продолжить установку? (y/n)"
|
||||
read -r confirm
|
||||
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
|
||||
info "Установка отменена"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Клонирование репозитория
|
||||
info "Клонирование репозитория..."
|
||||
|
||||
if [ -d "$INSTALL_DIR" ]; then
|
||||
warn "Директория $INSTALL_DIR уже существует"
|
||||
question "Удалить существующую директорию и продолжить? (y/n)"
|
||||
read -r confirm
|
||||
if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then
|
||||
rm -rf "$INSTALL_DIR"
|
||||
else
|
||||
error "Установка отменена"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Проверка наличия git
|
||||
if ! command -v git &> /dev/null; then
|
||||
info "Установка git..."
|
||||
if command -v apt-get &> /dev/null; then
|
||||
apt-get update -qq
|
||||
apt-get install -y git
|
||||
elif command -v yum &> /dev/null; then
|
||||
yum install -y git
|
||||
else
|
||||
error "Не удалось установить git. Установите его вручную."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
git clone "$REPO_URL" "$INSTALL_DIR"
|
||||
cd "$INSTALL_DIR"
|
||||
|
||||
# Запуск скрипта установки
|
||||
info "Запуск скрипта установки..."
|
||||
|
||||
INSTALL_CMD="./install.sh --hpb-url \"$HPB_URL\" --hpb-secret \"$HPB_SECRET\" --listen \"$LISTEN_ADDRESS\""
|
||||
|
||||
if [ -n "$F7CLOUD_URL" ] && [ -n "$F7CLOUD_SECRET" ]; then
|
||||
INSTALL_CMD="$INSTALL_CMD --f7cloud-url \"$F7CLOUD_URL\" --f7cloud-secret \"$F7CLOUD_SECRET\""
|
||||
fi
|
||||
|
||||
eval "$INSTALL_CMD"
|
||||
|
||||
info ""
|
||||
info "=========================================="
|
||||
info "Развертывание завершено успешно!"
|
||||
info "=========================================="
|
||||
info ""
|
||||
info "Сервер установлен в: $INSTALL_DIR"
|
||||
info ""
|
||||
info "Для запуска сервиса выполните:"
|
||||
info " sudo systemctl start f7cloud-talk-recording"
|
||||
info " sudo systemctl enable f7cloud-talk-recording"
|
||||
info ""
|
||||
info "Для проверки статуса:"
|
||||
info " sudo systemctl status f7cloud-talk-recording"
|
||||
info ""
|
||||
123
install.sh
123
install.sh
|
|
@ -94,9 +94,23 @@ while [[ $# -gt 0 ]]; do
|
|||
esac
|
||||
done
|
||||
|
||||
# Проверка обязательных параметров
|
||||
# Проверка обязательных параметров или переменных окружения
|
||||
if [ -z "$HPB_URL" ]; then
|
||||
HPB_URL="${HPB_URL_ENV:-}"
|
||||
fi
|
||||
if [ -z "$HPB_SECRET" ]; then
|
||||
HPB_SECRET="${HPB_SECRET_ENV:-}"
|
||||
fi
|
||||
if [ -z "$F7CLOUD_URL" ]; then
|
||||
F7CLOUD_URL="${F7CLOUD_URL_ENV:-}"
|
||||
fi
|
||||
if [ -z "$F7CLOUD_SECRET" ]; then
|
||||
F7CLOUD_SECRET="${F7CLOUD_SECRET_ENV:-}"
|
||||
fi
|
||||
|
||||
if [ -z "$HPB_URL" ] || [ -z "$HPB_SECRET" ]; then
|
||||
error "Ошибка: --hpb-url и --hpb-secret являются обязательными параметрами"
|
||||
error "Или используйте переменные окружения: HPB_URL_ENV и HPB_SECRET_ENV"
|
||||
echo ""
|
||||
show_help
|
||||
exit 1
|
||||
|
|
@ -148,7 +162,10 @@ if [ "$DISTRO" = "debian" ]; then
|
|||
libgconf-2-4 \
|
||||
libasound2-dev \
|
||||
curl \
|
||||
wget
|
||||
wget \
|
||||
git \
|
||||
bzip2 \
|
||||
tar
|
||||
else
|
||||
error "Установка для RedHat-based дистрибутивов пока не поддерживается автоматически"
|
||||
exit 1
|
||||
|
|
@ -157,19 +174,58 @@ fi
|
|||
# 2. Установка Firefox ESR
|
||||
info "Установка Firefox ESR..."
|
||||
|
||||
if [ -d "dependencies/firefox-esr" ]; then
|
||||
info "Копирование Firefox ESR из dependencies..."
|
||||
if [ -d "dependencies/firefox-esr" ] && [ -f "dependencies/firefox-esr/firefox" ]; then
|
||||
info "Использование Firefox ESR из dependencies..."
|
||||
cp -r dependencies/firefox-esr /opt/firefox-esr
|
||||
if [ -f /opt/firefox-esr/firefox ]; then
|
||||
ln -sf /opt/firefox-esr/firefox /usr/local/bin/firefox-esr
|
||||
chmod +x /usr/local/bin/firefox-esr
|
||||
info "Firefox ESR установлен в /opt/firefox-esr"
|
||||
else
|
||||
warn "Firefox ESR не найден в dependencies/, пытаемся скачать..."
|
||||
|
||||
# Определяем архитектуру
|
||||
ARCH=$(uname -m)
|
||||
if [ "$ARCH" = "x86_64" ]; then
|
||||
FIREFOX_ARCH="linux64"
|
||||
elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
|
||||
FIREFOX_ARCH="linux-aarch64"
|
||||
else
|
||||
error "Неподдерживаемая архитектура: $ARCH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "Скачивание Firefox ESR для $FIREFOX_ARCH..."
|
||||
mkdir -p dependencies
|
||||
cd dependencies
|
||||
|
||||
# Скачиваем последнюю версию Firefox ESR
|
||||
FIREFOX_URL="https://download.mozilla.org/?product=firefox-esr-latest-ssl&os=${FIREFOX_ARCH}&lang=en-US"
|
||||
|
||||
if command -v wget &> /dev/null; then
|
||||
wget -q --show-progress -O firefox-esr.tar.bz2 "$FIREFOX_URL"
|
||||
elif command -v curl &> /dev/null; then
|
||||
curl -L -o firefox-esr.tar.bz2 "$FIREFOX_URL"
|
||||
else
|
||||
error "Не найден wget или curl для скачивания Firefox ESR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f firefox-esr.tar.bz2 ]; then
|
||||
info "Распаковка Firefox ESR..."
|
||||
tar -xjf firefox-esr.tar.bz2
|
||||
mv firefox firefox-esr
|
||||
rm firefox-esr.tar.bz2
|
||||
|
||||
cp -r firefox-esr /opt/firefox-esr
|
||||
ln -sf /opt/firefox-esr/firefox /usr/local/bin/firefox-esr
|
||||
chmod +x /usr/local/bin/firefox-esr
|
||||
info "Firefox ESR установлен в /opt/firefox-esr"
|
||||
else
|
||||
error "Firefox ESR не найден в dependencies/firefox-esr"
|
||||
error "Не удалось скачать Firefox ESR"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
warn "Firefox ESR не найден в dependencies/, пропускаем..."
|
||||
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# 3. Установка Geckodriver
|
||||
|
|
@ -188,7 +244,56 @@ elif [ -f "dependencies/geckodriver.tar.gz" ]; then
|
|||
cd ..
|
||||
info "Geckodriver установлен в /usr/local/bin/geckodriver"
|
||||
else
|
||||
warn "Geckodriver не найден в dependencies/, пропускаем..."
|
||||
warn "Geckodriver не найден в dependencies/, пытаемся скачать..."
|
||||
|
||||
# Определяем архитектуру
|
||||
ARCH=$(uname -m)
|
||||
if [ "$ARCH" = "x86_64" ]; then
|
||||
GECKODRIVER_ARCH="linux64"
|
||||
elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
|
||||
GECKODRIVER_ARCH="linux-aarch64"
|
||||
else
|
||||
error "Неподдерживаемая архитектура: $ARCH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "Скачивание Geckodriver для $GECKODRIVER_ARCH..."
|
||||
mkdir -p dependencies
|
||||
cd dependencies
|
||||
|
||||
# Получаем последнюю версию geckodriver из GitHub releases
|
||||
GECKODRIVER_VERSION=$(curl -s https://api.github.com/repos/mozilla/geckodriver/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' | sed 's/v//')
|
||||
|
||||
if [ -z "$GECKODRIVER_VERSION" ]; then
|
||||
# Fallback на известную версию
|
||||
GECKODRIVER_VERSION="0.34.0"
|
||||
warn "Не удалось определить версию, используем $GECKODRIVER_VERSION"
|
||||
fi
|
||||
|
||||
GECKODRIVER_URL="https://github.com/mozilla/geckodriver/releases/download/v${GECKODRIVER_VERSION}/geckodriver-v${GECKODRIVER_VERSION}-${GECKODRIVER_ARCH}.tar.gz"
|
||||
|
||||
if command -v wget &> /dev/null; then
|
||||
wget -q --show-progress -O geckodriver.tar.gz "$GECKODRIVER_URL"
|
||||
elif command -v curl &> /dev/null; then
|
||||
curl -L -o geckodriver.tar.gz "$GECKODRIVER_URL"
|
||||
else
|
||||
error "Не найден wget или curl для скачивания Geckodriver"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f geckodriver.tar.gz ]; then
|
||||
info "Распаковка Geckodriver..."
|
||||
tar -xzf geckodriver.tar.gz
|
||||
chmod +x geckodriver
|
||||
cp geckodriver /usr/local/bin/geckodriver
|
||||
rm geckodriver.tar.gz
|
||||
info "Geckodriver установлен в /usr/local/bin/geckodriver"
|
||||
else
|
||||
error "Не удалось скачать Geckodriver"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# 4. Создание виртуального окружения Python
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user