server/scripts/versia-install.sh
2024-11-24 00:46:40 +01:00

400 lines
9.9 KiB
Bash
Executable file

#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'
# Configuration variables
RANDOM_SUFFIX=$(head /dev/urandom | tr -dc 'a-z0-9' | head -c 8)
DOMAIN="versia-${RANDOM_SUFFIX}.localhost"
INSTALL_DIR="./versia-install-${RANDOM_SUFFIX}"
COMPOSE_FILE="${INSTALL_DIR}/docker-compose.yml"
CONFIG_FILE="${INSTALL_DIR}/config/config.toml"
CONFIG_EXAMPLE="${INSTALL_DIR}/config/config.example.toml"
VERSION="v0.7.0"
PORT=$(shuf -i 10000-65000 -n 1)
# Store container names
CONTAINER_PREFIX="versia-${RANDOM_SUFFIX}"
CONTAINER_NAMES=(
"${CONTAINER_PREFIX}-server"
"${CONTAINER_PREFIX}-fe"
"${CONTAINER_PREFIX}-db"
"${CONTAINER_PREFIX}-redis"
)
# Function to log messages
log() {
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"
}
error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
exit 1
}
# Check for required commands
check_requirements() {
local required_commands=("docker" "docker-compose" "curl" "mkcert")
for cmd in "${required_commands[@]}"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
error "$cmd is required but not installed. Please install it first."
fi
done
}
# Create necessary directories
setup_directories() {
log "Creating installation directories..."
mkdir -p "${INSTALL_DIR}"/{config,logs,uploads,store,redis-data,db-data}
}
# Generate SSL certificates using mkcert
setup_ssl() {
log "Setting up SSL certificates..."
# Initialize mkcert if not already done
mkcert -install
# Generate certificates for the domain
cd "${INSTALL_DIR}"
mkcert "${DOMAIN}"
# Create nginx config directory if it doesn't exist
mkdir -p "${INSTALL_DIR}/nginx"
}
# Download necessary files
download_files() {
log "Downloading configuration files..."
# Download docker-compose.yml
curl -sSL "https://raw.githubusercontent.com/versia-pub/server/${VERSION}/docker-compose.yml" -o "${COMPOSE_FILE}"
# Download config.example.toml
curl -sSL "https://raw.githubusercontent.com/versia-pub/server/${VERSION}/config/config.example.toml" -o "${INSTALL_DIR}/config/config.example.toml"
}
# Generate random passwords
generate_passwords() {
POSTGRES_PASSWORD=$(openssl rand -base64 32)
REDIS_PASSWORD=$(openssl rand -base64 32)
}
# Configure Versia config.toml
configure_config_file() {
log "Configuring config.toml..."
cat > "${CONFIG_FILE}" << EOF
[database]
host = "db"
port = 5432
username = "versia"
password = "${POSTGRES_PASSWORD}"
database = "versia"
[redis.queue]
host = "redis"
port = 6379
password = "${REDIS_PASSWORD}"
database = 0
enabled = true
[redis.cache]
host = "redis"
port = 6379
password = "${REDIS_PASSWORD}"
database = 1
enabled = true
[sonic]
host = "sonic"
port = 1491
password = ""
enabled = false
[smtp]
# SMTP server to use for sending emails
server = "smtp.example.com"
port = 465
username = "test@example.com"
password = "password123"
tls = true
# Disable all email functions (this will allow people to sign up without verifying
# their email)
enabled = false
[filters]
# Regex filters for federated and local data
# Drops data matching the filters
# Does not apply retroactively to existing data
# Note contents
note_content = [
# "(https?://)?(www\\.)?youtube\\.com/watch\\?v=[a-zA-Z0-9_-]+",
# "(https?://)?(www\\.)?youtu\\.be/[a-zA-Z0-9_-]+",
]
emoji = []
# These will drop users matching the filters
username = []
displayname = []
bio = []
[ratelimits]
# These settings apply to every route at once
# Amount to multiply every route's duration by
duration_coeff = 1.0
# Amount to multiply every route's max requests per [duration] by
max_coeff = 1.0
[ratelimits.custom]
# Add in any API route in this style here
# Applies before the global ratelimit changes
# "/api/v1/accounts/:id/block" = { duration = 30, max = 60 }
# "/api/v1/timelines/public" = { duration = 60, max = 200 }
[signups]
registration = true
rules = [
"Do not harass others",
"Be nice to people",
"Don't spam",
"Don't post illegal content",
]
[http]
base_url = "https://${DOMAIN}:${PORT}"
bind = "0.0.0.0"
bind_port = ${PORT}
[http.tls]
enabled = true
key = "/app/dist/config/${DOMAIN}-key.pem"
cert = "/app/dist/config/${DOMAIN}.pem"
[frontend]
enabled = true
url = "http://fe:3000"
[media]
backend = "local"
deduplicate_media = true
local_uploads_folder = "uploads"
[media.conversion]
convert_images = true
convert_to = "image/webp"
convert_vector = false
[validation]
max_displayname_size = 50
max_bio_size = 5000
max_note_size = 5000
max_avatar_size = 5000000
max_header_size = 5000000
max_media_size = 40000000
max_media_attachments = 10
max_media_description_size = 1000
max_poll_options = 20
max_poll_option_size = 500
min_poll_duration = 60
max_poll_duration = 1893456000
max_username_size = 30
max_field_count = 10
max_field_name_size = 1000
max_field_value_size = 1000
[validation.challenges]
# "Challenges" (aka captchas) are a way to verify that a user is human
# Versia Server's challenges use no external services, and are Proof of Work based
# This means that they do not require any user interaction, instead
# they require the user's computer to do a small amount of work
enabled = false
# The difficulty of the challenge, higher is will take more time to solve
difficulty = 50000
# Challenge expiration time in seconds
expiration = 300 # 5 minutes
# Leave this empty to generate a new key
key = ""
[instance]
name = "Local Versia Instance"
description = "A local development instance of Versia Server"
[instance.keys]
public = "MCowBQYDK2VwAyEA39sO9bdtrZbyQ5+tKdQf4VIU/PY+Y5Zx7+3JL5Omxno="
private = "MC4CAQAwBQYDK2VwBCIEIMZXcDkHIqRFUmmZVw04l7nZjkzwlXfnQbH5iT1XCYVn"
[logging]
log_level = "debug"
log_ip = false
[logging.storage]
requests = "logs/requests.log"
[plugins.config."@versia/openid".keys]
public = "MCowBQYDK2VwAyEAfyZx8r98gVHtdH5EF1NYrBeChOXkt50mqiwKO2TX0f8="
private = "MC4CAQAwBQYDK2VwBCIEILDi1g7+bwNjBBvL4CRWHZpCFBR2m2OPCot62Wr+TCbq"
EOF
}
# Create a new docker-compose.yml with our modifications
create_docker_compose() {
cat > "${COMPOSE_FILE}" << EOF
services:
versia:
image: ghcr.io/versia-pub/server:main
volumes:
- ./logs:/app/dist/logs
- ./config:/app/dist/config
- ./uploads:/app/dist/uploads
restart: unless-stopped
container_name: ${CONTAINER_NAMES[0]}
tty: true
ports:
- ${PORT}:${PORT}
networks:
- ${CONTAINER_PREFIX}-net
depends_on:
- db
- redis
- fe
fe:
image: ghcr.io/versia-pub/frontend:main
container_name: ${CONTAINER_NAMES[1]}
restart: unless-stopped
networks:
- ${CONTAINER_PREFIX}-net
environment:
NUXT_PUBLIC_API_HOST: https://${DOMAIN}:${PORT}
db:
image: ghcr.io/versia-pub/postgres:main
container_name: ${CONTAINER_NAMES[2]}
restart: unless-stopped
environment:
POSTGRES_DB: versia
POSTGRES_USER: versia
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
networks:
- ${CONTAINER_PREFIX}-net
volumes:
- ./db-data:/var/lib/postgresql/data
redis:
image: redis:alpine
container_name: ${CONTAINER_NAMES[3]}
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- ./redis-data:/data
restart: unless-stopped
networks:
- ${CONTAINER_PREFIX}-net
networks:
${CONTAINER_PREFIX}-net:
EOF
}
# Function to create a new user and set password
create_user() {
local username="$1"
local password="$2"
log "Creating user: ${username}"
# Set the password using a heredoc to provide input
docker exec -i "${CONTAINER_NAMES[0]}" /bin/sh /app/entrypoint.sh cli user create "${username}" --password "${password}"
}
# Configure the services
configure_services() {
log "Configuring services..."
# Configure config.toml
configure_config_file
# Create new docker-compose.yml with our modifications
create_docker_compose
# Copy SSL certificates to config directory
cp "${INSTALL_DIR}/${DOMAIN}.pem" "${INSTALL_DIR}/config/"
cp "${INSTALL_DIR}/${DOMAIN}-key.pem" "${INSTALL_DIR}/config/"
}
# Cleanup function
cleanup() {
log "Cleaning up installation..."
cd "${INSTALL_DIR}"
docker-compose down -v
rm -rf "${INSTALL_DIR}"
log "Cleanup complete!"
}
# Function to handle SIGINT (Ctrl+C)
handle_interrupt() {
log "Received interrupt signal. Cleaning up..."
cleanup
exit 0
}
# Main installation function
install_versia() {
log "Starting Versia installation..."
check_requirements
setup_directories
generate_passwords
download_files
setup_ssl
configure_services
# Start the services
log "Starting Versia services..."
cd "${INSTALL_DIR}"
docker-compose up -d
# Wait for services to be ready
sleep 5
# Create a default test user
TEST_USER="testuser_${RANDOM_SUFFIX}"
TEST_PASSWORD=$(openssl rand -base64 12)
create_user "${TEST_USER}" "${TEST_PASSWORD}"
log "Installation complete! Versia is now available at https://${DOMAIN}:${PORT}"
log "Installation Details:"
log "---------------------"
log "Domain: ${DOMAIN}"
log "Port: ${PORT}"
log "Installation Directory: ${INSTALL_DIR}"
log "Test User: ${TEST_USER}"
log "Test Password: ${TEST_PASSWORD}"
log "PostgreSQL Password: ${POSTGRES_PASSWORD}"
log "Redis Password: ${REDIS_PASSWORD}"
log "Container Names:"
for name in "${CONTAINER_NAMES[@]}"; do
log " - ${name}"
done
log "---------------------"
log "To create additional users, use:"
log "docker-compose exec -it ${CONTAINER_NAMES[0]} /bin/sh /app/entrypoint.sh cli user create <username> --set-password"
log "---------------------"
log "Press Ctrl+C to stop and cleanup the installation"
# Set up interrupt handler
trap handle_interrupt SIGINT
# Wait indefinitely
while true; do
sleep 1
done
}
# Run the installation
install_versia