mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
Refactor setup scripts for improved reliability and clarity
Reworked both Windows (.bat) and Unix (.sh) setup scripts to improve error handling, logging, and user prompts. The scripts now check for prerequisites, handle Sentry enablement more clearly, ensure environment files are copied with error checks, and consolidate service startup into a single docker compose command with log output. Unused or redundant code was removed for maintainability.
This commit is contained in:
@@ -1,9 +1,15 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
goto :main
|
||||
REM Variables
|
||||
set SCRIPT_DIR=%~dp0
|
||||
set LOG_DIR=%SCRIPT_DIR%logs
|
||||
set REPO_DIR=%SCRIPT_DIR%..\..
|
||||
set CLONE_NEEDED=0
|
||||
set SENTRY_ENABLED=0
|
||||
set LOG_FILE=
|
||||
|
||||
REM --- Helper: Check command existence ---
|
||||
REM Helper: Check command existence
|
||||
:check_command
|
||||
if "%1"=="" (
|
||||
echo ERROR: check_command called with no command argument!
|
||||
@@ -26,25 +32,15 @@ echo AutoGPT Windows Setup
|
||||
echo =============================
|
||||
echo.
|
||||
|
||||
REM --- Variables ---
|
||||
set SCRIPT_DIR=%~dp0
|
||||
set LOG_DIR=%SCRIPT_DIR%logs
|
||||
set BACKEND_LOG=%LOG_DIR%\backend_setup.log
|
||||
set FRONTEND_LOG=%LOG_DIR%\frontend_setup.log
|
||||
set CLONE_NEEDED=0
|
||||
set REPO_DIR=%SCRIPT_DIR%..\..
|
||||
|
||||
REM --- Create logs folder immediately ---
|
||||
REM Create logs folder immediately
|
||||
if not exist "%LOG_DIR%" mkdir "%LOG_DIR%"
|
||||
|
||||
echo Checking prerequisites...
|
||||
call :check_command git Git
|
||||
call :check_command docker Docker
|
||||
call :check_command npm Node.js
|
||||
call :check_command pnpm pnpm
|
||||
echo.
|
||||
|
||||
REM --- Detect repo ---
|
||||
REM Detect repo
|
||||
if exist "%REPO_DIR%\.git" (
|
||||
set CLONE_NEEDED=0
|
||||
) else (
|
||||
@@ -52,7 +48,7 @@ if exist "%REPO_DIR%\.git" (
|
||||
set CLONE_NEEDED=1
|
||||
)
|
||||
|
||||
REM --- Clone repo if needed ---
|
||||
REM Clone repo if needed
|
||||
if %CLONE_NEEDED%==1 (
|
||||
echo Cloning AutoGPT repository...
|
||||
git clone https://github.com/Significant-Gravitas/AutoGPT.git "%REPO_DIR%"
|
||||
@@ -66,61 +62,73 @@ if %CLONE_NEEDED%==1 (
|
||||
)
|
||||
echo.
|
||||
|
||||
REM --- Prompt for Sentry enablement ---
|
||||
REM Prompt for Sentry enablement
|
||||
set SENTRY_ENABLED=0
|
||||
echo Would you like to enable debug information to be shared so we can fix your issues? [Y/n]
|
||||
echo Enable debug info sharing to help fix issues? [Y/n]
|
||||
set /p sentry_answer="Enable Sentry? [Y/n]: "
|
||||
if /I "%sentry_answer%"=="" set SENTRY_ENABLED=1
|
||||
if /I "%sentry_answer%"=="y" set SENTRY_ENABLED=1
|
||||
if /I "%sentry_answer%"=="yes" set SENTRY_ENABLED=1
|
||||
if /I "%sentry_answer%"=="n" set SENTRY_ENABLED=0
|
||||
if /I "%sentry_answer%"=="no" set SENTRY_ENABLED=0
|
||||
|
||||
REM --- Setup backend ---
|
||||
echo Setting up backend services...
|
||||
echo.
|
||||
cd /d "%REPO_DIR%\autogpt_platform"
|
||||
if exist .env.example copy /Y .env.example .env >nul
|
||||
cd backend
|
||||
if exist .env.example copy /Y .env.example .env >nul
|
||||
|
||||
REM --- Set SENTRY_DSN in backend/.env ---
|
||||
REM Navigate to autogpt_platform
|
||||
echo Setting up environment...
|
||||
cd /d "%REPO_DIR%\autogpt_platform"
|
||||
if errorlevel 1 (
|
||||
echo Failed to navigate to autogpt_platform directory.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Copy main .env
|
||||
if exist .env.example copy /Y .env.example .env >nul
|
||||
if errorlevel 1 (
|
||||
echo Failed to copy main .env file.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Configure backend Sentry
|
||||
cd backend
|
||||
if errorlevel 1 (
|
||||
echo Failed to navigate to backend directory.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if exist .env.example copy /Y .env.example .env >nul
|
||||
if errorlevel 1 (
|
||||
echo Failed to copy backend .env file.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
set SENTRY_DSN=https://11d0640fef35640e0eb9f022eb7d7626@o4505260022104064.ingest.us.sentry.io/4507890252447744
|
||||
if %SENTRY_ENABLED%==1 (
|
||||
powershell -Command "(Get-Content .env) -replace '^SENTRY_DSN=.*', 'SENTRY_DSN=%SENTRY_DSN%' | Set-Content .env"
|
||||
echo Sentry enabled in backend.
|
||||
echo Sentry enabled
|
||||
) else (
|
||||
powershell -Command "(Get-Content .env) -replace '^SENTRY_DSN=.*', 'SENTRY_DSN=' | Set-Content .env"
|
||||
echo Sentry not enabled in backend.
|
||||
echo Sentry disabled
|
||||
)
|
||||
|
||||
cd ..
|
||||
echo.
|
||||
|
||||
docker compose down > "%BACKEND_LOG%" 2>&1
|
||||
if errorlevel 1 echo (docker compose down failed, continuing...)
|
||||
docker compose up -d --build >> "%BACKEND_LOG%" 2>&1
|
||||
REM Run docker compose with logging
|
||||
echo Running docker compose up -d --build...
|
||||
set LOG_FILE=%REPO_DIR%\autogpt_platform\logs\docker_setup.log
|
||||
docker compose up -d --build > "%LOG_FILE%" 2>&1
|
||||
if errorlevel 1 (
|
||||
echo Backend setup failed. See log: %BACKEND_LOG%
|
||||
echo Docker compose failed. Check log file for details: %LOG_FILE%
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo Backend services started successfully.
|
||||
echo.
|
||||
|
||||
REM --- Setup frontend ---
|
||||
echo Setting up frontend application...
|
||||
echo Services started successfully.
|
||||
echo.
|
||||
cd frontend
|
||||
if exist .env.example copy /Y .env.example .env >nul
|
||||
call pnpm.cmd install
|
||||
if errorlevel 1 (
|
||||
echo pnpm install failed!
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo Frontend dependencies installed successfully.
|
||||
echo.
|
||||
|
||||
REM --- Setup complete ---
|
||||
echo Setup complete!
|
||||
echo Access AutoGPT at: http://localhost:3000
|
||||
echo To stop services, run "docker compose down" in %REPO_DIR%\autogpt_platform
|
||||
|
||||
@@ -14,24 +14,20 @@ GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
NC='\033[0m'
|
||||
|
||||
# Variables
|
||||
REPO_DIR=""
|
||||
CLONE_NEEDED=false
|
||||
DOCKER_CMD="docker"
|
||||
DOCKER_COMPOSE_CMD="docker compose"
|
||||
LOG_DIR=""
|
||||
SENTRY_ENABLED=0
|
||||
LOG_FILE=""
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Helper Functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Print colored text
|
||||
print_color() {
|
||||
printf "${!1}%s${NC}\n" "$2"
|
||||
}
|
||||
|
||||
# Print the ASCII banner
|
||||
print_banner() {
|
||||
print_color "BLUE" "
|
||||
d8888 888 .d8888b. 8888888b. 88888888888
|
||||
@@ -45,292 +41,120 @@ d88P 888 \"Y88888 \"Y888 \"Y88P\" \"Y8888P88 888 888
|
||||
"
|
||||
}
|
||||
|
||||
# Handle errors and exit
|
||||
handle_error() {
|
||||
echo ""
|
||||
print_color "RED" "Error: $1"
|
||||
print_color "YELLOW" "Press Enter to exit..."
|
||||
read -r
|
||||
if [ -n "$LOG_FILE" ] && [ -f "$LOG_FILE" ]; then
|
||||
print_color "RED" "Check log file for details: $LOG_FILE"
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Logging Functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Prepare log directory
|
||||
setup_logs() {
|
||||
LOG_DIR="$REPO_DIR/autogpt_platform/logs"
|
||||
mkdir -p "$LOG_DIR"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Health Check Functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Check service health by polling an endpoint
|
||||
check_health() {
|
||||
local url=$1
|
||||
local expected=$2
|
||||
local name=$3
|
||||
local max_attempts=$4
|
||||
local timeout=$5
|
||||
|
||||
if ! command -v curl &> /dev/null; then
|
||||
echo "curl not found. Skipping health check for $name."
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Checking $name health..."
|
||||
for ((attempt=1; attempt<=max_attempts; attempt++)); do
|
||||
echo "Attempt $attempt/$max_attempts"
|
||||
response=$(curl -s --max-time "$timeout" "$url")
|
||||
if [[ "$response" == *"$expected"* ]]; then
|
||||
echo "✓ $name is healthy"
|
||||
return 0
|
||||
fi
|
||||
echo "Waiting 5s before next attempt..."
|
||||
sleep 5
|
||||
done
|
||||
echo "✗ $name health check failed after $max_attempts attempts"
|
||||
return 1
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Prerequisite and Environment Functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Check for required commands
|
||||
check_command() {
|
||||
local cmd=$1
|
||||
local name=$2
|
||||
local url=$3
|
||||
|
||||
|
||||
if ! command -v "$cmd" &> /dev/null; then
|
||||
handle_error "$name is not installed. Please install it and try again. Visit $url"
|
||||
handle_error "$name is not installed. Please install it and try again."
|
||||
else
|
||||
print_color "GREEN" "✓ $name is installed"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check for optional commands
|
||||
check_command_optional() {
|
||||
local cmd=$1
|
||||
if command -v "$cmd" &> /dev/null; then
|
||||
print_color "GREEN" "✓ $cmd is installed"
|
||||
else
|
||||
print_color "YELLOW" "$cmd is not installed. Some features will be skipped."
|
||||
fi
|
||||
}
|
||||
|
||||
# Check Docker permissions and adjust commands if needed
|
||||
check_docker_permissions() {
|
||||
check_prerequisites() {
|
||||
print_color "BLUE" "Checking prerequisites..."
|
||||
check_command git "Git"
|
||||
check_command docker "Docker"
|
||||
|
||||
if ! docker info &> /dev/null; then
|
||||
print_color "YELLOW" "Docker requires elevated privileges. Using sudo for Docker commands..."
|
||||
print_color "YELLOW" "Using sudo for Docker commands..."
|
||||
DOCKER_CMD="sudo docker"
|
||||
DOCKER_COMPOSE_CMD="sudo docker compose"
|
||||
fi
|
||||
|
||||
print_color "GREEN" "All prerequisites installed!"
|
||||
}
|
||||
|
||||
# Check all prerequisites
|
||||
check_prerequisites() {
|
||||
print_color "GREEN" "AutoGPT's Automated Setup Script"
|
||||
print_color "GREEN" "-------------------------------"
|
||||
print_color "BLUE" "This script will automatically install and set up AutoGPT for you."
|
||||
echo ""
|
||||
print_color "YELLOW" "Checking prerequisites:"
|
||||
|
||||
check_command git "Git" "https://git-scm.com/downloads"
|
||||
check_command docker "Docker" "https://docs.docker.com/get-docker/"
|
||||
check_docker_permissions
|
||||
check_command npm "npm (Node.js)" "https://nodejs.org/en/download/"
|
||||
check_command pnpm "pnpm (Node.js package manager)" "https://pnpm.io/installation"
|
||||
check_command_optional curl "curl"
|
||||
|
||||
print_color "GREEN" "All prerequisites are installed! Starting installation..."
|
||||
echo ""
|
||||
prompt_sentry() {
|
||||
print_color "YELLOW" "Enable debug info sharing to help fix issues? [Y/n]"
|
||||
read -r sentry_answer
|
||||
case "${sentry_answer,,}" in
|
||||
""|y|yes) SENTRY_ENABLED=1 ;;
|
||||
*) SENTRY_ENABLED=0 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Detect installation mode and set repo directory
|
||||
# (Clones if not in a repo, otherwise uses current directory)
|
||||
detect_installation_mode() {
|
||||
detect_repo() {
|
||||
if [[ "$PWD" == */autogpt_platform/installer ]]; then
|
||||
if [[ -d "../../.git" ]]; then
|
||||
REPO_DIR="$(cd ../..; pwd)"
|
||||
CLONE_NEEDED=false
|
||||
cd ../.. || handle_error "Failed to navigate to repository root."
|
||||
cd ../.. || handle_error "Failed to navigate to repo root"
|
||||
else
|
||||
CLONE_NEEDED=true
|
||||
REPO_DIR="$(pwd)/AutoGPT"
|
||||
cd "$(dirname \"$(dirname \"$(dirname \"$PWD\")\")\")" || handle_error "Failed to navigate to parent directory."
|
||||
fi
|
||||
elif [[ -d ".git" && -d "autogpt_platform/installer" ]]; then
|
||||
REPO_DIR="$PWD"
|
||||
CLONE_NEEDED=false
|
||||
else
|
||||
CLONE_NEEDED=true
|
||||
REPO_DIR="$(pwd)/AutoGPT"
|
||||
fi
|
||||
}
|
||||
|
||||
# Clone the repository if needed
|
||||
clone_repository() {
|
||||
clone_repo() {
|
||||
if [ "$CLONE_NEEDED" = true ]; then
|
||||
print_color "BLUE" "Cloning AutoGPT repository..."
|
||||
if git clone https://github.com/Significant-Gravitas/AutoGPT.git "$REPO_DIR"; then
|
||||
print_color "GREEN" "✓ Repo cloned successfully!"
|
||||
else
|
||||
handle_error "Failed to clone the repository."
|
||||
fi
|
||||
else
|
||||
print_color "GREEN" "Using existing AutoGPT repository"
|
||||
git clone https://github.com/Significant-Gravitas/AutoGPT.git "$REPO_DIR" || handle_error "Failed to clone repository"
|
||||
fi
|
||||
}
|
||||
|
||||
# Prompt for Sentry enablement and set global flag
|
||||
prompt_sentry_enablement() {
|
||||
print_color "YELLOW" "Would you like to enable debug information to be shared so we can fix your issues? [Y/n]"
|
||||
read -r sentry_answer
|
||||
case "${sentry_answer,,}" in
|
||||
""|y|yes)
|
||||
SENTRY_ENABLED=1
|
||||
;;
|
||||
n|no)
|
||||
SENTRY_ENABLED=0
|
||||
;;
|
||||
*)
|
||||
print_color "YELLOW" "Invalid input. Defaulting to yes. Sentry will be enabled."
|
||||
SENTRY_ENABLED=1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Setup Functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Set up backend services and configure Sentry if enabled
|
||||
setup_backend() {
|
||||
print_color "BLUE" "Setting up backend services..."
|
||||
cd "$REPO_DIR/autogpt_platform" || handle_error "Failed to navigate to backend directory."
|
||||
cp .env.example .env || handle_error "Failed to copy environment file."
|
||||
|
||||
# Set SENTRY_DSN in backend/.env
|
||||
cd backend || handle_error "Failed to navigate to backend subdirectory."
|
||||
cp .env.example .env || handle_error "Failed to copy backend environment file."
|
||||
sentry_url="https://11d0640fef35640e0eb9f022eb7d7626@o4505260022104064.ingest.us.sentry.io/4507890252447744"
|
||||
setup_env() {
|
||||
cd "$REPO_DIR/autogpt_platform" || handle_error "Failed to navigate to autogpt_platform"
|
||||
|
||||
# Copy main .env
|
||||
cp .env.example .env || handle_error "Failed to copy main .env"
|
||||
|
||||
# Configure backend Sentry
|
||||
cd backend || handle_error "Failed to navigate to backend"
|
||||
cp .env.example .env || handle_error "Failed to copy backend .env"
|
||||
|
||||
local sentry_url="https://11d0640fef35640e0eb9f022eb7d7626@o4505260022104064.ingest.us.sentry.io/4507890252447744"
|
||||
if [ "$SENTRY_ENABLED" = "1" ]; then
|
||||
sed -i "s|^SENTRY_DSN=.*$|SENTRY_DSN=$sentry_url|" .env || echo "SENTRY_DSN=$sentry_url" >> .env
|
||||
print_color "GREEN" "Sentry enabled in backend."
|
||||
print_color "GREEN" "Sentry enabled"
|
||||
else
|
||||
sed -i "s|^SENTRY_DSN=.*$|SENTRY_DSN=|" .env || echo "SENTRY_DSN=" >> .env
|
||||
print_color "YELLOW" "Sentry not enabled in backend."
|
||||
fi
|
||||
cd .. # back to autogpt_platform
|
||||
|
||||
$DOCKER_COMPOSE_CMD down || handle_error "Failed to stop existing backend services."
|
||||
$DOCKER_COMPOSE_CMD up -d --build || handle_error "Failed to start backend services."
|
||||
print_color "GREEN" "✓ Backend services started successfully"
|
||||
|
||||
cd ..
|
||||
}
|
||||
|
||||
# Set up frontend application
|
||||
setup_frontend() {
|
||||
print_color "BLUE" "Setting up frontend application..."
|
||||
cd "$REPO_DIR/autogpt_platform/frontend" || handle_error "Failed to navigate to frontend directory."
|
||||
cp .env.example .env || handle_error "Failed to copy frontend environment file."
|
||||
corepack enable || handle_error "Failed to enable corepack."
|
||||
pnpm install || handle_error "Failed to install frontend dependencies."
|
||||
print_color "GREEN" "✓ Frontend dependencies installed successfully"
|
||||
}
|
||||
|
||||
# Run backend and frontend setup concurrently and manage logs
|
||||
run_concurrent_setup() {
|
||||
setup_logs
|
||||
backend_log="$LOG_DIR/backend_setup.log"
|
||||
frontend_log="$LOG_DIR/frontend_setup.log"
|
||||
|
||||
: > "$backend_log"
|
||||
: > "$frontend_log"
|
||||
|
||||
setup_backend > "$backend_log" 2>&1 &
|
||||
backend_pid=$!
|
||||
echo "Backend setup finished."
|
||||
|
||||
setup_frontend > "$frontend_log" 2>&1 &
|
||||
frontend_pid=$!
|
||||
echo "Frontend setup finished."
|
||||
|
||||
show_spinner "$backend_pid" "$frontend_pid"
|
||||
|
||||
wait $backend_pid; backend_status=$?
|
||||
wait $frontend_pid; frontend_status=$?
|
||||
|
||||
if [ $backend_status -ne 0 ]; then
|
||||
print_color "RED" "Backend setup failed. See log: $backend_log"
|
||||
exit 1
|
||||
run_docker() {
|
||||
print_color "BLUE" "Running docker compose up -d --build..."
|
||||
mkdir -p logs
|
||||
LOG_FILE="$REPO_DIR/autogpt_platform/logs/docker_setup.log"
|
||||
|
||||
if $DOCKER_COMPOSE_CMD up -d --build > "$LOG_FILE" 2>&1; then
|
||||
print_color "GREEN" "✓ Services started successfully"
|
||||
else
|
||||
handle_error "Docker compose failed"
|
||||
fi
|
||||
|
||||
if [ $frontend_status -ne 0 ]; then
|
||||
print_color "RED" "Frontend setup failed. See log: $frontend_log"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
# Show a spinner while background jobs run
|
||||
show_spinner() {
|
||||
local backend_pid=$1
|
||||
local frontend_pid=$2
|
||||
spin='-\|/'
|
||||
i=0
|
||||
messages=("Working..." "Still working..." "Setting up dependencies..." "Almost there...")
|
||||
msg_index=0
|
||||
msg_counter=0
|
||||
clear_line=" "
|
||||
|
||||
while kill -0 $backend_pid 2>/dev/null || kill -0 $frontend_pid 2>/dev/null; do
|
||||
i=$(( (i+1) % 4 ))
|
||||
msg_counter=$(( (msg_counter+1) % 300 ))
|
||||
if [ $msg_counter -eq 0 ]; then
|
||||
msg_index=$(( (msg_index+1) % ${#messages[@]} ))
|
||||
fi
|
||||
printf "\r${clear_line}\r${YELLOW}[%c]${NC} %s" "${spin:$i:1}" "${messages[$msg_index]}"
|
||||
sleep .1
|
||||
done
|
||||
printf "\r${clear_line}\r${GREEN}[✓]${NC} Setup completed!\n"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Main Entry Point
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
main() {
|
||||
print_banner
|
||||
print_color "GREEN" "AutoGPT Setup Script"
|
||||
print_color "GREEN" "-------------------"
|
||||
|
||||
check_prerequisites
|
||||
prompt_sentry_enablement
|
||||
detect_installation_mode
|
||||
clone_repository
|
||||
setup_logs
|
||||
run_concurrent_setup
|
||||
|
||||
print_color "YELLOW" "Waiting for services to start..."
|
||||
sleep 20
|
||||
|
||||
print_color "YELLOW" "Verifying services health..."
|
||||
check_health "http://localhost:8006/health" "\"status\":\"healthy\"" "Backend" 6 15
|
||||
check_health "http://localhost:3000/health" "Yay im healthy" "Frontend" 6 15
|
||||
|
||||
if [ $backend_status -ne 0 ] || [ $frontend_status -ne 0 ]; then
|
||||
print_color "RED" "Setup failed. See logs for details."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
prompt_sentry
|
||||
detect_repo
|
||||
clone_repo
|
||||
setup_env
|
||||
run_docker
|
||||
|
||||
print_color "GREEN" "Setup complete!"
|
||||
print_color "BLUE" "Access AutoGPT at: http://localhost:3000"
|
||||
print_color "YELLOW" "To stop services, run 'docker compose down' in $REPO_DIR/autogpt_platform"
|
||||
echo ""
|
||||
print_color "GREEN" "Press Enter to exit (services will keep running)..."
|
||||
read -r
|
||||
print_color "YELLOW" "To stop: 'docker compose down' in $REPO_DIR/autogpt_platform"
|
||||
}
|
||||
|
||||
main
|
||||
main
|
||||
Reference in New Issue
Block a user