663 lines
24 KiB
Bash
663 lines
24 KiB
Bash
#!/bin/bash
|
|
#
|
|
# LetsBe Server Setup Script
|
|
# This script sets up the server and deploys selected tools.
|
|
#
|
|
# Usage:
|
|
# ./setup.sh --tools "all" --domain "example.com"
|
|
# ./setup.sh --tools "portainer,n8n,baserow" --domain "example.com"
|
|
# ./setup.sh --tools "1,2,3"
|
|
# ./setup.sh # Foundation only, no tools deployed
|
|
#
|
|
# Arguments:
|
|
# --tools Comma-separated list of tools to deploy, "all", or tool numbers
|
|
# --domain Domain name for SSL email (administrator@domain)
|
|
# --skip-ssl Skip SSL certificate setup (useful for testing)
|
|
#
|
|
|
|
set -euo pipefail
|
|
|
|
# =============================================================================
|
|
# ARGUMENT PARSING
|
|
# =============================================================================
|
|
|
|
TOOLS_TO_DEPLOY=""
|
|
SKIP_SSL=false
|
|
ROOT_SSL=false
|
|
DOMAIN=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--tools)
|
|
TOOLS_TO_DEPLOY="$2"
|
|
shift 2
|
|
;;
|
|
--domain)
|
|
DOMAIN="$2"
|
|
shift 2
|
|
;;
|
|
--skip-ssl)
|
|
SKIP_SSL=true
|
|
shift
|
|
;;
|
|
--root-ssl)
|
|
ROOT_SSL=true
|
|
shift
|
|
;;
|
|
--help|-h)
|
|
echo "Usage: $0 [--tools \"tool1,tool2,...\"|\"all\"] [--domain DOMAIN] [--skip-ssl] [--root-ssl]"
|
|
echo ""
|
|
echo "Options:"
|
|
echo " --tools Comma-separated list of tools, 'all', or tool numbers"
|
|
echo " --domain Domain name for SSL email (administrator@domain)"
|
|
echo " --skip-ssl Skip SSL certificate setup"
|
|
echo " --root-ssl Include root domain in SSL certificate"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " $0 --tools \"all\" --domain \"example.com\""
|
|
echo " $0 --tools \"portainer,n8n,baserow\""
|
|
echo " $0 --tools \"1,5,10\""
|
|
echo " $0 # Foundation only"
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unknown option: $1"
|
|
echo "Use --help for usage information"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
echo "=== LetsBe Server Setup ==="
|
|
echo ""
|
|
|
|
# =============================================================================
|
|
# PACKAGE INSTALLATION
|
|
# =============================================================================
|
|
|
|
echo "[1/10] Installing system packages..."
|
|
sudo apt update && sudo apt upgrade -y
|
|
sudo apt install -y build-essential net-tools tree wget jq nano curl htop ufw fail2ban unattended-upgrades apt-listchanges apticron git gnupg ca-certificates apache2-utils acl certbot python3-certbot-nginx rsync rclone s3cmd zip sudo iptables htop dstat openssl
|
|
|
|
# =============================================================================
|
|
# DOCKER INSTALLATION
|
|
# =============================================================================
|
|
|
|
echo "[2/10] Installing Docker..."
|
|
sudo install -m 0755 -d /etc/apt/keyrings
|
|
# Use --batch and --yes for non-interactive gpg (required for nohup/background execution)
|
|
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /tmp/docker.gpg
|
|
sudo gpg --batch --yes --dearmor -o /etc/apt/keyrings/docker.gpg /tmp/docker.gpg
|
|
rm -f /tmp/docker.gpg
|
|
sudo chmod a+r /etc/apt/keyrings/docker.gpg
|
|
sudo echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
sudo apt update
|
|
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
|
sudo systemctl enable docker
|
|
|
|
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-linux-$(uname -m)" -o /usr/local/bin/docker-compose
|
|
sudo chmod 755 /usr/local/bin/docker-compose
|
|
|
|
# =============================================================================
|
|
# DISABLE CONFLICTING SERVICES
|
|
# =============================================================================
|
|
|
|
echo "[3/10] Disabling conflicting services..."
|
|
sudo systemctl stop exim4 2>/dev/null || true
|
|
sudo systemctl disable exim4 2>/dev/null || true
|
|
|
|
sudo systemctl stop apache2 2>/dev/null || true
|
|
sudo systemctl disable apache2 2>/dev/null || true
|
|
sudo apt remove -y apache2 2>/dev/null || true
|
|
|
|
# =============================================================================
|
|
# NGINX INSTALLATION & CONFIGURATION
|
|
# =============================================================================
|
|
|
|
echo "[4/10] Installing and configuring nginx..."
|
|
sudo apt install -y nginx
|
|
sudo systemctl enable nginx
|
|
|
|
sudo rm -f /etc/nginx/sites-enabled/default
|
|
|
|
openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com" -keyout /etc/nginx/placeholder.key -out /etc/nginx/placeholder.crt
|
|
|
|
cat <<EOF > /etc/nginx/conf.d/fallback.conf
|
|
server {
|
|
listen 80 default_server;
|
|
listen [::]:80 default_server;
|
|
server_name _;
|
|
return 444;
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl default_server;
|
|
server_name _;
|
|
return 444;
|
|
|
|
ssl_certificate /etc/nginx/placeholder.crt;
|
|
ssl_certificate_key /etc/nginx/placeholder.key;
|
|
}
|
|
EOF
|
|
|
|
sudo systemctl restart nginx
|
|
|
|
# =============================================================================
|
|
# FIREWALL CONFIGURATION
|
|
# =============================================================================
|
|
|
|
echo "[5/10] Configuring UFW firewall..."
|
|
ufw allow 22
|
|
ufw allow 22022
|
|
ufw allow 80
|
|
ufw allow 443
|
|
ufw allow 25
|
|
ufw allow 587
|
|
ufw allow 143
|
|
ufw allow 110
|
|
ufw allow 4190
|
|
ufw allow 465
|
|
ufw allow 993
|
|
ufw allow 995
|
|
ufw --force enable
|
|
|
|
# =============================================================================
|
|
# USER SETUP - STEFAN (DO NOT MODIFY)
|
|
# =============================================================================
|
|
|
|
echo "[6/10] Configuring user 'stefan'..."
|
|
USER="stefan"
|
|
PUBLIC_KEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINV9ptnNgA4+haqLWh9bOilydlX2LIlAZqjfaDN5qEPf calorie_preset200@simplelogin.com"
|
|
|
|
if ! id -u $USER > /dev/null 2>&1; then
|
|
echo "User $USER does not exist, will be created."
|
|
useradd -m -s /bin/bash $USER
|
|
fi
|
|
|
|
mkdir -p /home/$USER/.ssh
|
|
chmod 700 /home/$USER/.ssh
|
|
|
|
echo "$PUBLIC_KEY" >> /home/$USER/.ssh/authorized_keys
|
|
chmod 600 /home/$USER/.ssh/authorized_keys
|
|
chown -R $USER:$USER /home/$USER/.ssh
|
|
|
|
usermod -aG docker $USER
|
|
|
|
echo "Public key was added for user $USER."
|
|
|
|
# =============================================================================
|
|
# SSH SECURITY HARDENING
|
|
# =============================================================================
|
|
|
|
echo "[7/10] Hardening SSH configuration..."
|
|
cat <<EOF > /etc/ssh/sshd_config
|
|
Include /etc/ssh/sshd_config.d/*.conf
|
|
|
|
Port 22022
|
|
#AddressFamily any
|
|
#ListenAddress 0.0.0.0
|
|
#ListenAddress ::
|
|
|
|
#HostKey /etc/ssh/ssh_host_rsa_key
|
|
#HostKey /etc/ssh/ssh_host_ecdsa_key
|
|
#HostKey /etc/ssh/ssh_host_ed25519_key
|
|
|
|
SyslogFacility AUTH
|
|
LogLevel VERBOSE
|
|
|
|
LoginGraceTime 2m
|
|
PermitRootLogin yes
|
|
#StrictModes yes
|
|
MaxAuthTries 6
|
|
MaxSessions 10
|
|
|
|
PasswordAuthentication yes
|
|
PermitEmptyPasswords no
|
|
|
|
ChallengeResponseAuthentication no
|
|
|
|
UsePAM yes
|
|
|
|
X11Forwarding yes
|
|
PrintMotd no
|
|
PrintLastLog yes
|
|
|
|
AcceptEnv LANG LC_*
|
|
|
|
Subsystem sftp /usr/lib/openssh/sftp-server
|
|
|
|
UsePrivilegeSeparation sandbox
|
|
AuthenticationMethods publickey
|
|
EOF
|
|
|
|
# NOTE: SSH restart moved to end of script to keep connection alive
|
|
|
|
# =============================================================================
|
|
# AUTOMATIC SECURITY UPDATES
|
|
# =============================================================================
|
|
|
|
echo "[8/10] Configuring automatic security updates..."
|
|
cat <<EOF > /etc/apt/apt.conf.d/20auto-upgrades
|
|
// Enable the update/upgrade script (0=disable)
|
|
APT::Periodic::Enable "1";
|
|
|
|
// Do "apt-get update" automatically every n-days (0=disable)
|
|
APT::Periodic::Update-Package-Lists "1";
|
|
|
|
// Do "apt-get upgrade --download-only" every n-days (0=disable)
|
|
APT::Periodic::Download-Upgradeable-Packages "1";
|
|
|
|
// Do "apt-get autoclean" every n-days (0=disable)
|
|
APT::Periodic::AutocleanInterval "7";
|
|
|
|
// Send report mail to root
|
|
// 0: no report (or null string)
|
|
// 1: progress report (actually any string)
|
|
// 2: + command outputs (remove -qq, remove 2>/dev/null, add -d)
|
|
APT::Periodic::Unattended-Upgrade "1";
|
|
|
|
// Automatically upgrade packages from these
|
|
Unattended-Upgrade::Origins-Pattern {
|
|
// "o=Debian,a=stable";
|
|
// "o=Debian,a=stable-updates";
|
|
"origin=Debian,codename=\${distro_codename},label=Debian-Security";
|
|
};
|
|
|
|
// You can specify your own packages to NOT automatically upgrade here
|
|
Unattended-Upgrade::Package-Blacklist {
|
|
};
|
|
|
|
// Run dpkg --force-confold --configure -a if a unclean dpkg state is detected to true to ensure that updates get installed even when the system got interrupted during a previous run
|
|
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
|
|
|
|
// Perform the upgrade when the machine is running because we wont be shutting our server down often
|
|
Unattended-Upgrade::InstallOnShutdown "false";
|
|
|
|
// Send an email to this address with information about the packages upgraded.
|
|
Unattended-Upgrade::Mail "administrator@letsbe.biz";
|
|
|
|
// Always send an e-mail
|
|
Unattended-Upgrade::MailOnlyOnError "true";
|
|
|
|
// Remove all unused dependencies after the upgrade has finished
|
|
Unattended-Upgrade::Remove-Unused-Dependencies "true";
|
|
|
|
// Remove any new unused dependencies after the upgrade has finished
|
|
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
|
|
|
|
// Automatically reboot WITHOUT CONFIRMATION if the file /var/run/reboot-required is found after the upgrade.
|
|
Unattended-Upgrade::Automatic-Reboot "false";
|
|
|
|
// Automatically reboot even if users are logged in.
|
|
Unattended-Upgrade::Automatic-Reboot-WithUsers "false";
|
|
EOF
|
|
|
|
# =============================================================================
|
|
# BACKUP SCRIPT & CRON (DISABLED - TODO: fix for non-interactive mode)
|
|
# =============================================================================
|
|
|
|
echo "Skipping backup script setup (will be configured manually later)..."
|
|
chmod 750 /opt/letsbe/scripts/backups.sh 2>/dev/null || true
|
|
mkdir -p /root/.config/rclone
|
|
|
|
# NOTE: Backup cron disabled - crontab hangs in non-interactive mode
|
|
# To enable manually after setup:
|
|
# crontab -e
|
|
# Add: 0 2 * * * /bin/bash /opt/letsbe/scripts/backups.sh >> /var/log/letsbe-backups.log 2>&1
|
|
|
|
# =============================================================================
|
|
# TOOL DEPLOYMENT
|
|
# =============================================================================
|
|
|
|
echo "[9/10] Deploying tools..."
|
|
|
|
# Get list of available tools
|
|
mapfile -t available_tools < <(ls /opt/letsbe/stacks/*/docker-compose.yml 2>/dev/null | xargs -I {} dirname {} | xargs -I {} basename {})
|
|
|
|
if [[ -z "$TOOLS_TO_DEPLOY" ]]; then
|
|
echo "No tools specified. Skipping tool deployment."
|
|
echo "Available tools: ${available_tools[*]}"
|
|
echo "Use --tools to deploy tools later."
|
|
else
|
|
# Determine which tools to deploy
|
|
declare -a tools_list=()
|
|
|
|
if [[ "$TOOLS_TO_DEPLOY" == "all" || "$TOOLS_TO_DEPLOY" == "a" ]]; then
|
|
tools_list=("${available_tools[@]}")
|
|
else
|
|
# Parse comma-separated list
|
|
IFS=',' read -ra requested_tools <<< "$TOOLS_TO_DEPLOY"
|
|
for tool in "${requested_tools[@]}"; do
|
|
tool=$(echo "$tool" | xargs) # Trim whitespace
|
|
|
|
# Check if it's a number (index)
|
|
if [[ "$tool" =~ ^[0-9]+$ ]]; then
|
|
idx=$((tool - 1))
|
|
if [[ $idx -ge 0 && $idx -lt ${#available_tools[@]} ]]; then
|
|
tools_list+=("${available_tools[$idx]}")
|
|
else
|
|
echo "Warning: Tool index $tool out of range, skipping."
|
|
fi
|
|
else
|
|
# It's a tool name
|
|
if [[ " ${available_tools[*]} " =~ " ${tool} " ]]; then
|
|
tools_list+=("$tool")
|
|
else
|
|
echo "Warning: Tool '$tool' not found, skipping."
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
|
|
echo "Deploying tools: ${tools_list[*]}"
|
|
|
|
# Track deployed tools for SSL setup
|
|
DEPLOYED_TOOLS=()
|
|
|
|
for tool_name in "${tools_list[@]}"; do
|
|
compose_file="/opt/letsbe/stacks/${tool_name}/docker-compose.yml"
|
|
|
|
if [[ -f "$compose_file" ]]; then
|
|
# Copy .env file to centralized env directory if it exists
|
|
stack_env="/opt/letsbe/stacks/${tool_name}/.env"
|
|
central_env="/opt/letsbe/env/${tool_name}.env"
|
|
if [[ -f "$stack_env" ]]; then
|
|
cp "$stack_env" "$central_env"
|
|
chmod 640 "$central_env"
|
|
echo "Copied env file for $tool_name"
|
|
fi
|
|
|
|
# Tool-specific pre-deployment setup
|
|
if [[ "$tool_name" == "nextcloud" ]]; then
|
|
echo "Creating Nextcloud bind mount directories..."
|
|
mkdir -p /opt/letsbe/config/nextcloud
|
|
mkdir -p /opt/letsbe/data/nextcloud
|
|
# Set appropriate ownership for www-data (uid 33 in Nextcloud container)
|
|
chown -R 33:33 /opt/letsbe/config/nextcloud
|
|
chown -R 33:33 /opt/letsbe/data/nextcloud
|
|
fi
|
|
|
|
if [[ "$tool_name" == "sysadmin" ]]; then
|
|
echo "Cloning/updating sysadmin repository..."
|
|
SYSADMIN_DIR="/opt/letsbe/stacks/sysadmin"
|
|
SYSADMIN_REPO="https://code.letsbe.solutions/letsbe/letsbe-sysadmin.git"
|
|
|
|
# Save our docker-compose.yml before clone
|
|
if [[ -f "${SYSADMIN_DIR}/docker-compose.yml" ]]; then
|
|
cp "${SYSADMIN_DIR}/docker-compose.yml" /tmp/sysadmin-compose.yml
|
|
fi
|
|
|
|
# Clone or pull the repo
|
|
if [[ -d "${SYSADMIN_DIR}/.git" ]]; then
|
|
echo " Pulling latest changes..."
|
|
cd "${SYSADMIN_DIR}" && git pull origin main || git pull origin master
|
|
else
|
|
echo " Cloning repository..."
|
|
# Clone into temp, then move contents
|
|
rm -rf /tmp/letsbe-sysadmin
|
|
git clone "${SYSADMIN_REPO}" /tmp/letsbe-sysadmin
|
|
# Move repo contents to sysadmin dir (preserving our docker-compose)
|
|
cp -r /tmp/letsbe-sysadmin/* "${SYSADMIN_DIR}/" 2>/dev/null || true
|
|
cp -r /tmp/letsbe-sysadmin/.* "${SYSADMIN_DIR}/" 2>/dev/null || true
|
|
rm -rf /tmp/letsbe-sysadmin
|
|
fi
|
|
|
|
# Restore our docker-compose.yml (with template variables)
|
|
if [[ -f /tmp/sysadmin-compose.yml ]]; then
|
|
cp /tmp/sysadmin-compose.yml "${SYSADMIN_DIR}/docker-compose.yml"
|
|
rm /tmp/sysadmin-compose.yml
|
|
fi
|
|
|
|
echo " Building sysadmin image..."
|
|
docker-compose -f "$compose_file" build
|
|
fi
|
|
|
|
echo "Starting $tool_name..."
|
|
docker-compose -f "$compose_file" up -d
|
|
|
|
# Tool-specific post-deployment initialization
|
|
if [[ "$tool_name" == "chatwoot" ]]; then
|
|
echo "Initializing Chatwoot database (pgvector + migrations)..."
|
|
|
|
# Get the customer prefix from the container name
|
|
CHATWOOT_POSTGRES=$(docker ps --format '{{.Names}}' | grep chatwoot-postgres | head -1)
|
|
CHATWOOT_RAILS=$(docker ps --format '{{.Names}}' | grep chatwoot-rails | head -1)
|
|
|
|
if [[ -n "$CHATWOOT_POSTGRES" && -n "$CHATWOOT_RAILS" ]]; then
|
|
# Wait for Postgres to be ready
|
|
echo " Waiting for Postgres to be ready..."
|
|
for i in {1..30}; do
|
|
if docker exec "$CHATWOOT_POSTGRES" pg_isready -U chatwoot -d chatwoot_production >/dev/null 2>&1; then
|
|
echo " Postgres is ready."
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
# Create pgvector extension
|
|
echo " Creating pgvector extension..."
|
|
docker exec "$CHATWOOT_POSTGRES" psql -U chatwoot -d chatwoot_production -c "CREATE EXTENSION IF NOT EXISTS vector;" 2>/dev/null || true
|
|
|
|
# Wait for Rails container to be fully up
|
|
echo " Waiting for Rails container..."
|
|
sleep 10
|
|
|
|
# Run database migrations
|
|
echo " Running Chatwoot database prepare..."
|
|
docker exec "$CHATWOOT_RAILS" bundle exec rails db:chatwoot_prepare 2>&1 || echo " Note: db:chatwoot_prepare may have already run"
|
|
|
|
echo " Chatwoot initialization complete."
|
|
else
|
|
echo " Warning: Could not find Chatwoot containers for initialization"
|
|
fi
|
|
fi
|
|
|
|
# Link nginx config if exists
|
|
nginx_conf="/opt/letsbe/nginx/${tool_name}.conf"
|
|
if [[ -f "$nginx_conf" ]]; then
|
|
cp "$nginx_conf" /etc/nginx/sites-available/
|
|
ln -sf /etc/nginx/sites-available/${tool_name}.conf /etc/nginx/sites-enabled/
|
|
echo "Nginx config linked for $tool_name"
|
|
DEPLOYED_TOOLS+=("$tool_name")
|
|
else
|
|
echo "No nginx config for $tool_name (may not need one)"
|
|
fi
|
|
else
|
|
echo "Warning: docker-compose.yml not found for $tool_name"
|
|
fi
|
|
done
|
|
|
|
# Restart nginx to apply new configs
|
|
systemctl restart nginx
|
|
fi
|
|
|
|
# =============================================================================
|
|
# SYSADMIN AGENT (Always deployed)
|
|
# =============================================================================
|
|
|
|
echo "[9.5/10] Deploying sysadmin agent..."
|
|
|
|
SYSADMIN_COMPOSE="/opt/letsbe/stacks/sysadmin/docker-compose.yml"
|
|
if [[ -f "$SYSADMIN_COMPOSE" ]]; then
|
|
# Check if sysadmin is already running
|
|
if docker ps --format '{{.Names}}' | grep -q "agent$"; then
|
|
echo "Sysadmin agent already running, updating..."
|
|
fi
|
|
|
|
SYSADMIN_DIR="/opt/letsbe/stacks/sysadmin"
|
|
SYSADMIN_REPO="https://code.letsbe.solutions/letsbe/letsbe-sysadmin.git"
|
|
|
|
# Save our docker-compose.yml before clone
|
|
cp "${SYSADMIN_DIR}/docker-compose.yml" /tmp/sysadmin-compose.yml
|
|
|
|
# Clone or pull the repo
|
|
if [[ -d "${SYSADMIN_DIR}/.git" ]]; then
|
|
echo " Pulling latest sysadmin changes..."
|
|
cd "${SYSADMIN_DIR}" && git pull origin main || git pull origin master || true
|
|
else
|
|
echo " Cloning sysadmin repository..."
|
|
rm -rf /tmp/letsbe-sysadmin
|
|
git clone "${SYSADMIN_REPO}" /tmp/letsbe-sysadmin
|
|
cp -r /tmp/letsbe-sysadmin/* "${SYSADMIN_DIR}/" 2>/dev/null || true
|
|
cp -r /tmp/letsbe-sysadmin/.* "${SYSADMIN_DIR}/" 2>/dev/null || true
|
|
rm -rf /tmp/letsbe-sysadmin
|
|
fi
|
|
|
|
# Restore our docker-compose.yml (with template variables replaced)
|
|
cp /tmp/sysadmin-compose.yml "${SYSADMIN_DIR}/docker-compose.yml"
|
|
rm /tmp/sysadmin-compose.yml
|
|
|
|
echo " Building sysadmin image..."
|
|
docker-compose -f "$SYSADMIN_COMPOSE" build
|
|
|
|
echo " Starting sysadmin agent..."
|
|
docker-compose -f "$SYSADMIN_COMPOSE" up -d
|
|
|
|
echo "Sysadmin agent deployed successfully."
|
|
else
|
|
echo "Warning: Sysadmin docker-compose.yml not found at $SYSADMIN_COMPOSE"
|
|
fi
|
|
|
|
# Collect domains from deployed tools' nginx configs (for SSL)
|
|
SSL_DOMAINS=()
|
|
if [[ ${#DEPLOYED_TOOLS[@]} -gt 0 ]]; then
|
|
for tool_name in "${DEPLOYED_TOOLS[@]}"; do
|
|
tool_conf="/etc/nginx/sites-enabled/${tool_name}.conf"
|
|
if [[ -f "$tool_conf" ]]; then
|
|
# Extract server_name values (excluding placeholders and _)
|
|
while IFS= read -r domain; do
|
|
if [[ -n "$domain" && "$domain" != "_" && ! "$domain" =~ \{\{ ]]; then
|
|
SSL_DOMAINS+=("$domain")
|
|
fi
|
|
done < <(grep -h "server_name" "$tool_conf" 2>/dev/null | awk '{print $2}' | tr -d ';' | sort -u)
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# =============================================================================
|
|
# SSL CERTIFICATE SETUP
|
|
# =============================================================================
|
|
|
|
echo "[10/10] Setting up SSL certificates..."
|
|
|
|
# NOTE: Certbot cron disabled - crontab hangs in non-interactive mode
|
|
# Certbot installs its own systemd timer, so manual cron not needed
|
|
echo "Certbot renewal handled by systemd timer (certbot.timer)"
|
|
|
|
if [[ "$SKIP_SSL" == "true" ]]; then
|
|
echo "Skipping SSL setup (--skip-ssl flag set)"
|
|
elif [[ ${#SSL_DOMAINS[@]} -eq 0 ]]; then
|
|
echo "No deployed tools with valid domains found."
|
|
echo "Skipping SSL setup. Either:"
|
|
echo " - No tools were deployed, or"
|
|
echo " - Templates not replaced (run env_setup.sh first with --domain parameter)"
|
|
echo "To manually setup SSL later: certbot --nginx -d yourdomain.com"
|
|
else
|
|
# Remove duplicates from SSL_DOMAINS
|
|
SSL_DOMAINS=($(printf '%s\n' "${SSL_DOMAINS[@]}" | sort -u))
|
|
|
|
# Add root domain if --root-ssl flag is set
|
|
if [[ "$ROOT_SSL" == "true" && -n "$DOMAIN" ]]; then
|
|
# Check if root domain is not already in the list
|
|
if [[ ! " ${SSL_DOMAINS[*]} " =~ " ${DOMAIN} " ]]; then
|
|
SSL_DOMAINS+=("$DOMAIN")
|
|
echo "Including root domain: $DOMAIN"
|
|
fi
|
|
fi
|
|
|
|
echo "----"
|
|
echo "Setting up SSL certificates for deployed tools:"
|
|
for domain in "${SSL_DOMAINS[@]}"; do
|
|
echo " - $domain"
|
|
done
|
|
echo ""
|
|
echo "Make sure DNS entries point to this server IP before proceeding."
|
|
|
|
# Derive email from domain parameter or use default
|
|
if [[ -n "$DOMAIN" ]]; then
|
|
SSL_EMAIL="administrator@${DOMAIN}"
|
|
else
|
|
# Try to extract base domain from first SSL domain
|
|
FIRST_DOMAIN="${SSL_DOMAINS[0]}"
|
|
# Extract base domain (remove subdomain)
|
|
BASE_DOMAIN=$(echo "$FIRST_DOMAIN" | awk -F. '{if(NF>2) print $(NF-1)"."$NF; else print $0}')
|
|
SSL_EMAIL="administrator@${BASE_DOMAIN}"
|
|
fi
|
|
|
|
echo "Using email: $SSL_EMAIL"
|
|
|
|
# Build domain arguments for certbot
|
|
DOMAIN_ARGS=""
|
|
for domain in "${SSL_DOMAINS[@]}"; do
|
|
DOMAIN_ARGS="$DOMAIN_ARGS -d $domain"
|
|
done
|
|
|
|
# Run certbot non-interactively with specific domains
|
|
sudo certbot --nginx \
|
|
--non-interactive \
|
|
--agree-tos \
|
|
--email "$SSL_EMAIL" \
|
|
--redirect \
|
|
$DOMAIN_ARGS \
|
|
|| echo "Certbot completed (some domains may have failed - check DNS)"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# COMPLETION SUMMARY
|
|
# =============================================================================
|
|
|
|
echo ""
|
|
echo "----"
|
|
echo "Configured domains:"
|
|
for conf_file in /etc/nginx/sites-enabled/*.conf; do
|
|
if [[ -f "$conf_file" ]]; then
|
|
server_names=$(grep -E "^\s*server_name\s+" "$conf_file" 2>/dev/null | awk '{print $2}' | tr -d ';' | sort | uniq)
|
|
for server_name in $server_names; do
|
|
if [[ "$server_name" != "_" ]]; then
|
|
echo " - $server_name ($conf_file)"
|
|
fi
|
|
done
|
|
fi
|
|
done
|
|
|
|
SERVER_IP=$(curl -4 -s ifconfig.co)
|
|
|
|
echo ""
|
|
echo "=============================================="
|
|
echo " LetsBe Server Setup Complete"
|
|
echo "=============================================="
|
|
echo ""
|
|
echo "Server IP: $SERVER_IP"
|
|
echo "SSH Port: 22022"
|
|
echo "SSH User: stefan (key-based auth only)"
|
|
echo ""
|
|
echo "Portainer (if deployed): https://$SERVER_IP:9443"
|
|
echo ""
|
|
echo "Important:"
|
|
echo " - Configure rclone for backups: rclone config"
|
|
echo " - SSH port changed to 22022"
|
|
echo " - User 'stefan' has sudo access (key in /home/stefan/.ssh/)"
|
|
echo ""
|
|
echo "=============================================="
|
|
|
|
# =============================================================================
|
|
# MARK SETUP AS COMPLETE (before SSH restart)
|
|
# =============================================================================
|
|
|
|
touch /opt/letsbe/.setup_installed
|
|
echo "Setup marked as complete."
|
|
|
|
# =============================================================================
|
|
# RESTART SSH (MUST BE LAST - This will disconnect the session!)
|
|
# =============================================================================
|
|
|
|
echo ""
|
|
echo "Restarting SSH on port 22022... (connection will drop)"
|
|
echo "Reconnect with: ssh -i id_ed25519 -p 22022 stefan@$SERVER_IP"
|
|
echo ""
|
|
|
|
# Small delay to ensure output is sent before disconnect
|
|
sleep 2
|
|
|
|
systemctl restart sshd
|