408 lines
13 KiB
Bash
408 lines
13 KiB
Bash
#!/usr/bin/env bash
|
||
|
||
# OpenWebUI LXC Installation Script
|
||
# Description: Install OpenWebUI - LLM Web Interface with Ollama integration
|
||
# OS: Debian 12 (Bookworm)
|
||
# Ports: Web UI: 8080
|
||
# Repository: https://github.com/open-webui/open-webui
|
||
# Last Updated: 2026-01
|
||
|
||
set -euo pipefail
|
||
|
||
#################################################################
|
||
# Configuration Variables
|
||
#################################################################
|
||
|
||
# Container Configuration
|
||
CT_ID=${CT_ID:-27002}
|
||
CT_HOSTNAME=${CT_HOSTNAME:-"eunha-openwebui"}
|
||
CT_CORES=${CT_CORES:-4}
|
||
CT_MEMORY=${CT_MEMORY:-8192}
|
||
CT_SWAP=${CT_SWAP:-4096}
|
||
CT_DISK_SIZE=${CT_DISK_SIZE:-50}
|
||
|
||
# Network Configuration
|
||
CT_IP=${CT_IP:-"dhcp"}
|
||
CT_GATEWAY=${CT_GATEWAY:-""}
|
||
CT_BRIDGE=${CT_BRIDGE:-"vmbr0"}
|
||
CT_NAMESERVER=${CT_NAMESERVER:-"8.8.8.8"}
|
||
|
||
# Storage Configuration
|
||
CT_STORAGE=${CT_STORAGE:-"local-lvm"}
|
||
TEMPLATE_STORAGE=${TEMPLATE_STORAGE:-"local"}
|
||
|
||
# Debian Template
|
||
DEBIAN_VERSION="12"
|
||
TEMPLATE_NAME=""
|
||
|
||
# OpenWebUI Configuration
|
||
OPENWEBUI_PORT=${OPENWEBUI_PORT:-8080}
|
||
OPENWEBUI_DATA="/opt/open-webui"
|
||
OLLAMA_HOST=${OLLAMA_HOST:-"http://host.docker.internal:11434"}
|
||
|
||
# Container Options - privileged for Docker support
|
||
CT_ONBOOT=${CT_ONBOOT:-1}
|
||
CT_UNPRIVILEGED=0
|
||
CT_FEATURES="keyctl=1,nesting=1"
|
||
|
||
#################################################################
|
||
# Color Output Functions
|
||
#################################################################
|
||
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m'
|
||
|
||
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||
success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||
|
||
#################################################################
|
||
# Validation Functions
|
||
#################################################################
|
||
|
||
check_root() {
|
||
if [[ $EUID -ne 0 ]]; then
|
||
error "This script must be run as root"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
check_proxmox() {
|
||
if ! command -v pct &> /dev/null; then
|
||
error "This script must be run on a Proxmox VE host"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
check_container_exists() {
|
||
if pct status "$CT_ID" &> /dev/null; then
|
||
error "Container ID $CT_ID already exists"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
detect_and_download_template() {
|
||
info "Updating template database..."
|
||
pveam update 2>&1 | grep -q "update successful\|already up to date" || true
|
||
|
||
info "Detecting available Debian ${DEBIAN_VERSION} template..."
|
||
local available_template
|
||
available_template=$(pveam available --section system 2>/dev/null | grep "debian-${DEBIAN_VERSION}" | grep "standard" | tail -1 | awk '{print $2}')
|
||
|
||
if [[ -z "$available_template" ]]; then
|
||
error "No Debian ${DEBIAN_VERSION} template found"
|
||
exit 1
|
||
fi
|
||
|
||
TEMPLATE_NAME="$available_template"
|
||
info "Found template: $TEMPLATE_NAME"
|
||
|
||
if pveam list "$TEMPLATE_STORAGE" 2>/dev/null | grep -q "$TEMPLATE_NAME"; then
|
||
success "Template already downloaded"
|
||
return 0
|
||
fi
|
||
|
||
warn "Downloading Debian template..."
|
||
pveam download "$TEMPLATE_STORAGE" "$TEMPLATE_NAME" 2>&1
|
||
success "Template downloaded successfully"
|
||
}
|
||
|
||
#################################################################
|
||
# Container Creation Functions
|
||
#################################################################
|
||
|
||
create_container() {
|
||
info "Creating LXC container $CT_ID ($CT_HOSTNAME)..."
|
||
|
||
local net_config="name=eth0,bridge=${CT_BRIDGE},ip=${CT_IP}"
|
||
if [[ "$CT_IP" != "dhcp" ]] && [[ -n "$CT_GATEWAY" ]]; then
|
||
net_config="${net_config},gw=${CT_GATEWAY}"
|
||
fi
|
||
|
||
pct create "$CT_ID" \
|
||
"${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE_NAME}" \
|
||
--hostname "$CT_HOSTNAME" \
|
||
--cores "$CT_CORES" \
|
||
--memory "$CT_MEMORY" \
|
||
--swap "$CT_SWAP" \
|
||
--rootfs "${CT_STORAGE}:${CT_DISK_SIZE}" \
|
||
--net0 "$net_config" \
|
||
--nameserver "$CT_NAMESERVER" \
|
||
--onboot "$CT_ONBOOT" \
|
||
--unprivileged "$CT_UNPRIVILEGED" \
|
||
--features "$CT_FEATURES" \
|
||
--ostype debian || {
|
||
error "Failed to create container"
|
||
exit 1
|
||
}
|
||
|
||
success "Container $CT_ID created successfully"
|
||
}
|
||
|
||
start_container() {
|
||
info "Starting container $CT_ID..."
|
||
pct start "$CT_ID" || {
|
||
error "Failed to start container"
|
||
exit 1
|
||
}
|
||
|
||
info "Waiting for container to boot..."
|
||
sleep 5
|
||
|
||
local max_attempts=30
|
||
local attempt=0
|
||
while [[ $attempt -lt $max_attempts ]]; do
|
||
if pct exec "$CT_ID" -- test -f /etc/os-release 2>/dev/null; then
|
||
success "Container is running"
|
||
return 0
|
||
fi
|
||
sleep 1
|
||
((attempt++))
|
||
done
|
||
|
||
error "Container failed to start properly"
|
||
exit 1
|
||
}
|
||
|
||
configure_autologin() {
|
||
info "Configuring root autologin..."
|
||
pct exec "$CT_ID" -- bash -c 'mkdir -p /etc/systemd/system/console-getty.service.d/'
|
||
pct exec "$CT_ID" -- bash -c 'cat > /etc/systemd/system/console-getty.service.d/autologin.conf << EOF
|
||
[Service]
|
||
ExecStart=
|
||
ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud console 115200,38400,9600 \$TERM
|
||
EOF'
|
||
pct exec "$CT_ID" -- bash -c 'systemctl daemon-reload'
|
||
success "Autologin configured"
|
||
}
|
||
|
||
#################################################################
|
||
# Application Installation Functions
|
||
#################################################################
|
||
|
||
install_dependencies() {
|
||
info "Installing dependencies..."
|
||
pct exec "$CT_ID" -- bash -c "apt-get update -qq"
|
||
pct exec "$CT_ID" -- bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
|
||
curl ca-certificates gnupg git"
|
||
success "Dependencies installed"
|
||
}
|
||
|
||
install_docker() {
|
||
info "Installing Docker..."
|
||
|
||
pct exec "$CT_ID" -- bash -c '
|
||
install -m 0755 -d /etc/apt/keyrings
|
||
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||
chmod a+r /etc/apt/keyrings/docker.gpg
|
||
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" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||
apt-get update -qq
|
||
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||
'
|
||
|
||
pct exec "$CT_ID" -- bash -c "systemctl enable docker && systemctl start docker"
|
||
success "Docker installed"
|
||
}
|
||
|
||
install_openwebui() {
|
||
info "Installing OpenWebUI..."
|
||
|
||
# Create data directory
|
||
pct exec "$CT_ID" -- bash -c "mkdir -p ${OPENWEBUI_DATA}"
|
||
|
||
# Create docker-compose.yml
|
||
pct exec "$CT_ID" -- bash -c "cat > ${OPENWEBUI_DATA}/docker-compose.yml << 'COMPOSE_EOF'
|
||
services:
|
||
open-webui:
|
||
image: ghcr.io/open-webui/open-webui:main
|
||
container_name: open-webui
|
||
restart: unless-stopped
|
||
ports:
|
||
- \"${OPENWEBUI_PORT}:8080\"
|
||
volumes:
|
||
- open-webui-data:/app/backend/data
|
||
environment:
|
||
- OLLAMA_BASE_URL=${OLLAMA_HOST}
|
||
- WEBUI_AUTH=true
|
||
- ENABLE_SIGNUP=true
|
||
- ENABLE_API_KEY=true
|
||
extra_hosts:
|
||
- \"host.docker.internal:host-gateway\"
|
||
|
||
volumes:
|
||
open-webui-data:
|
||
COMPOSE_EOF"
|
||
|
||
# Update compose file with actual values
|
||
pct exec "$CT_ID" -- bash -c "sed -i 's|\${OPENWEBUI_PORT}|${OPENWEBUI_PORT}|g' ${OPENWEBUI_DATA}/docker-compose.yml"
|
||
pct exec "$CT_ID" -- bash -c "sed -i 's|\${OLLAMA_HOST}|${OLLAMA_HOST}|g' ${OPENWEBUI_DATA}/docker-compose.yml"
|
||
|
||
# Create systemd service
|
||
pct exec "$CT_ID" -- bash -c "cat > /etc/systemd/system/openwebui.service << 'SERVICE_EOF'
|
||
[Unit]
|
||
Description=OpenWebUI Container
|
||
Requires=docker.service
|
||
After=docker.service
|
||
|
||
[Service]
|
||
Type=oneshot
|
||
RemainAfterExit=yes
|
||
WorkingDirectory=/opt/open-webui
|
||
ExecStart=/usr/bin/docker compose up -d
|
||
ExecStop=/usr/bin/docker compose down
|
||
TimeoutStartSec=300
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
SERVICE_EOF"
|
||
|
||
# Pull and start
|
||
info "Pulling OpenWebUI Docker image (this may take a while)..."
|
||
pct exec "$CT_ID" -- bash -c "cd ${OPENWEBUI_DATA} && docker compose pull"
|
||
pct exec "$CT_ID" -- bash -c "systemctl daemon-reload && systemctl enable openwebui && systemctl start openwebui"
|
||
|
||
# Wait for OpenWebUI to start
|
||
info "Waiting for OpenWebUI to start..."
|
||
local max_attempts=60
|
||
local attempt=0
|
||
while [[ $attempt -lt $max_attempts ]]; do
|
||
if pct exec "$CT_ID" -- bash -c "curl -s -o /dev/null -w '%{http_code}' http://localhost:${OPENWEBUI_PORT}" 2>/dev/null | grep -q "200\|301\|302"; then
|
||
success "OpenWebUI is running"
|
||
return 0
|
||
fi
|
||
sleep 5
|
||
((attempt++))
|
||
if [[ $((attempt % 6)) -eq 0 ]]; then
|
||
info "Still waiting... ($((attempt * 5))s elapsed)"
|
||
fi
|
||
done
|
||
|
||
warn "OpenWebUI may still be starting. Check: pct exec $CT_ID -- docker logs open-webui"
|
||
}
|
||
|
||
add_container_notes() {
|
||
info "Adding container notes..."
|
||
|
||
local container_ip
|
||
if [[ "$CT_IP" == "dhcp" ]]; then
|
||
sleep 3
|
||
container_ip=$(pct exec "$CT_ID" -- hostname -I 2>/dev/null | awk '{print $1}')
|
||
if [[ -z "$container_ip" ]]; then
|
||
container_ip="[DHCP - check after boot]"
|
||
fi
|
||
else
|
||
container_ip="${CT_IP%/*}"
|
||
fi
|
||
|
||
local notes="OpenWebUI - LLM Web Interface
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
|
||
📋 CONTAINER DETAILS
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
Container ID: ${CT_ID}
|
||
Hostname: ${CT_HOSTNAME}
|
||
IP Address: ${container_ip}
|
||
CPU Cores: ${CT_CORES}
|
||
Memory: ${CT_MEMORY}MB
|
||
Disk Size: ${CT_DISK_SIZE}GB
|
||
|
||
🌐 APPLICATION ACCESS
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
Web UI: http://${container_ip}:${OPENWEBUI_PORT}
|
||
Ollama Host: ${OLLAMA_HOST}
|
||
|
||
📧 SETUP INSTRUCTIONS
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
1. Access Web UI and create admin account
|
||
2. Configure Ollama connection if needed
|
||
3. Add LLM models via Ollama
|
||
|
||
🔧 SERVICE MANAGEMENT
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
Status: pct exec ${CT_ID} -- docker ps
|
||
Logs: pct exec ${CT_ID} -- docker logs -f open-webui
|
||
Restart: pct exec ${CT_ID} -- systemctl restart openwebui
|
||
Stop: pct exec ${CT_ID} -- systemctl stop openwebui
|
||
Start: pct exec ${CT_ID} -- systemctl start openwebui
|
||
|
||
📦 CONTAINER MANAGEMENT
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
Enter: pct enter ${CT_ID}
|
||
Start: pct start ${CT_ID}
|
||
Stop: pct stop ${CT_ID}
|
||
Delete: pct destroy ${CT_ID}
|
||
|
||
ℹ️ AUTO-GENERATED BY: proxmox-infra/proxmox-lxc-shell-commands"
|
||
|
||
pct set "$CT_ID" -description "$notes" 2>/dev/null || true
|
||
success "Container notes added"
|
||
}
|
||
|
||
display_info() {
|
||
local container_ip
|
||
if [[ "$CT_IP" == "dhcp" ]]; then
|
||
container_ip=$(pct exec "$CT_ID" -- hostname -I | awk '{print $1}')
|
||
else
|
||
container_ip="${CT_IP%/*}"
|
||
fi
|
||
|
||
echo ""
|
||
echo "================================================================="
|
||
success "OpenWebUI LXC Container Setup Complete!"
|
||
echo "================================================================="
|
||
echo ""
|
||
info "All access information has been saved to container Notes"
|
||
echo ""
|
||
echo "Container Details:"
|
||
echo " • Container ID: $CT_ID"
|
||
echo " • Hostname: $CT_HOSTNAME"
|
||
echo " • IP Address: $container_ip"
|
||
echo ""
|
||
echo "Application Access:"
|
||
echo " • Web UI: http://${container_ip}:${OPENWEBUI_PORT}"
|
||
echo " • Ollama Host: ${OLLAMA_HOST}"
|
||
echo ""
|
||
echo "Setup Instructions:"
|
||
echo " 1. Access Web UI and create admin account"
|
||
echo " 2. First registered user becomes admin"
|
||
echo " 3. Configure Ollama connection settings"
|
||
echo ""
|
||
echo "Service Management:"
|
||
echo " • Status: pct exec $CT_ID -- docker ps"
|
||
echo " • Logs: pct exec $CT_ID -- docker logs -f open-webui"
|
||
echo " • Restart: pct exec $CT_ID -- systemctl restart openwebui"
|
||
echo ""
|
||
echo "================================================================="
|
||
}
|
||
|
||
#################################################################
|
||
# Main Execution
|
||
#################################################################
|
||
|
||
main() {
|
||
info "Starting OpenWebUI LXC container creation..."
|
||
echo ""
|
||
|
||
check_root
|
||
check_proxmox
|
||
check_container_exists
|
||
detect_and_download_template
|
||
|
||
create_container
|
||
start_container
|
||
configure_autologin
|
||
|
||
install_dependencies
|
||
install_docker
|
||
install_openwebui
|
||
|
||
add_container_notes
|
||
display_info
|
||
}
|
||
|
||
main "$@"
|