- proxmox-lxc-mailcow.sh: Docker 기반 메일 서버 (4GB+ RAM) - proxmox-lxc-metabase.sh: BI/데이터 분석 도구 (PostgreSQL 포함) - proxmox-lxc-flowiseai.sh: LangChain 기반 AI 워크플로우 빌더 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
499 lines
17 KiB
Bash
Executable File
499 lines
17 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# Metabase LXC Installation Script
|
|
# Description: Metabase - Open source BI and data analytics platform
|
|
# OS: Debian 12 (Bookworm) - Auto-detected latest version
|
|
# Ports: Web UI: 3000
|
|
# Repository: https://github.com/metabase/metabase
|
|
# Last Updated: 2026-01-05
|
|
|
|
set -euo pipefail
|
|
|
|
#################################################################
|
|
# Configuration Variables
|
|
#################################################################
|
|
|
|
# Container Configuration
|
|
CT_ID=${CT_ID:-26005} # Container ID
|
|
CT_HOSTNAME=${CT_HOSTNAME:-"metabase"} # Container hostname
|
|
CT_CORES=${CT_CORES:-2} # CPU cores
|
|
CT_MEMORY=${CT_MEMORY:-2048} # RAM in MB
|
|
CT_SWAP=${CT_SWAP:-512} # Swap in MB
|
|
CT_DISK_SIZE=${CT_DISK_SIZE:-20} # Root disk size in GB
|
|
|
|
# Network Configuration
|
|
CT_IP=${CT_IP:-"dhcp"} # IP address (dhcp or static like 192.168.1.100/24)
|
|
CT_GATEWAY=${CT_GATEWAY:-""} # Gateway (required for static IP)
|
|
CT_BRIDGE=${CT_BRIDGE:-"vmbr0"} # Network bridge
|
|
CT_NAMESERVER=${CT_NAMESERVER:-"8.8.8.8"} # DNS server
|
|
|
|
# Storage Configuration
|
|
CT_STORAGE=${CT_STORAGE:-"local-lvm"} # Storage pool for container
|
|
TEMPLATE_STORAGE=${TEMPLATE_STORAGE:-"local"} # Storage pool for templates
|
|
|
|
# Debian Template (will be auto-detected)
|
|
DEBIAN_VERSION="12" # Debian version
|
|
TEMPLATE_NAME="" # Auto-detected
|
|
|
|
# Metabase Configuration
|
|
METABASE_PORT=${METABASE_PORT:-3000} # Web UI port
|
|
METABASE_DB_TYPE=${METABASE_DB_TYPE:-"postgres"} # Database type (postgres or h2)
|
|
POSTGRES_USER=${POSTGRES_USER:-"metabase"} # PostgreSQL user
|
|
POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-"$(openssl rand -base64 16 | tr -dc 'a-zA-Z0-9' | head -c 16)"}
|
|
POSTGRES_DB=${POSTGRES_DB:-"metabase_db"} # PostgreSQL database name
|
|
|
|
# Container Options - IMPORTANT: Privileged required for Docker-in-LXC
|
|
CT_ONBOOT=${CT_ONBOOT:-1} # Start on boot (1=yes, 0=no)
|
|
CT_UNPRIVILEGED=${CT_UNPRIVILEGED:-0} # Must be 0 (privileged) for Docker
|
|
CT_FEATURES=${CT_FEATURES:-"keyctl=1,nesting=1"} # Container features for Docker
|
|
|
|
#################################################################
|
|
# Color Output Functions
|
|
#################################################################
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
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"
|
|
info "Please choose a different CT_ID or remove the existing container"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
detect_and_download_template() {
|
|
info "Updating template database..."
|
|
|
|
if ! pveam update 2>&1 | grep -q "update successful\|already up to date"; then
|
|
warn "Template database update encountered issues, continuing anyway..."
|
|
fi
|
|
|
|
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 in available templates"
|
|
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 (this may take a few minutes)..."
|
|
if pveam download "$TEMPLATE_STORAGE" "$TEMPLATE_NAME" 2>&1; then
|
|
success "Template downloaded successfully"
|
|
else
|
|
error "Failed to download template"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
#################################################################
|
|
# 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
|
|
|
|
success "Container started successfully"
|
|
}
|
|
|
|
configure_autologin() {
|
|
info "Configuring automatic console login..."
|
|
|
|
pct exec "$CT_ID" -- bash -c "mkdir -p /etc/systemd/system/container-getty@1.service.d"
|
|
|
|
pct exec "$CT_ID" -- bash -c 'cat > /etc/systemd/system/container-getty@1.service.d/override.conf << EOF
|
|
[Service]
|
|
ExecStart=
|
|
ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 \$TERM
|
|
EOF'
|
|
|
|
pct exec "$CT_ID" -- bash -c "systemctl daemon-reload"
|
|
pct exec "$CT_ID" -- bash -c "systemctl restart container-getty@1.service" 2>/dev/null || true
|
|
|
|
success "Automatic console login configured"
|
|
}
|
|
|
|
#################################################################
|
|
# Docker Installation
|
|
#################################################################
|
|
|
|
install_docker() {
|
|
info "Installing Docker in container $CT_ID..."
|
|
|
|
info "Updating package list..."
|
|
pct exec "$CT_ID" -- bash -c "apt-get update -qq"
|
|
|
|
info "Installing required packages..."
|
|
pct exec "$CT_ID" -- bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
|
|
ca-certificates \
|
|
curl \
|
|
gnupg \
|
|
lsb-release"
|
|
|
|
info "Adding Docker GPG key..."
|
|
pct exec "$CT_ID" -- bash -c "install -m 0755 -d /etc/apt/keyrings"
|
|
pct exec "$CT_ID" -- bash -c "curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc"
|
|
pct exec "$CT_ID" -- bash -c "chmod a+r /etc/apt/keyrings/docker.asc"
|
|
|
|
info "Adding Docker repository..."
|
|
pct exec "$CT_ID" -- bash -c 'echo \
|
|
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
|
|
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
|
|
tee /etc/apt/sources.list.d/docker.list > /dev/null'
|
|
|
|
info "Installing Docker Engine..."
|
|
pct exec "$CT_ID" -- bash -c "apt-get update -qq"
|
|
pct exec "$CT_ID" -- bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
|
|
docker-ce \
|
|
docker-ce-cli \
|
|
containerd.io \
|
|
docker-buildx-plugin \
|
|
docker-compose-plugin"
|
|
|
|
pct exec "$CT_ID" -- bash -c "systemctl enable docker"
|
|
pct exec "$CT_ID" -- bash -c "systemctl start docker"
|
|
|
|
sleep 3
|
|
|
|
if pct exec "$CT_ID" -- bash -c "docker --version" &>/dev/null; then
|
|
success "Docker installed successfully"
|
|
else
|
|
error "Docker installation failed"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
#################################################################
|
|
# Metabase Installation
|
|
#################################################################
|
|
|
|
install_metabase() {
|
|
info "Installing Metabase in container $CT_ID..."
|
|
|
|
# Create directory structure
|
|
info "Creating Metabase directories..."
|
|
pct exec "$CT_ID" -- bash -c "mkdir -p /opt/metabase/{data,plugins}"
|
|
|
|
# Create docker-compose.yml
|
|
info "Creating docker-compose configuration..."
|
|
pct exec "$CT_ID" -- bash -c "cat > /opt/metabase/docker-compose.yml << 'EOF'
|
|
version: '3.8'
|
|
|
|
services:
|
|
metabase:
|
|
image: metabase/metabase:latest
|
|
container_name: metabase
|
|
restart: always
|
|
ports:
|
|
- \"${METABASE_PORT}:3000\"
|
|
environment:
|
|
- MB_DB_TYPE=postgres
|
|
- MB_DB_DBNAME=${POSTGRES_DB}
|
|
- MB_DB_PORT=5432
|
|
- MB_DB_USER=${POSTGRES_USER}
|
|
- MB_DB_PASS=${POSTGRES_PASSWORD}
|
|
- MB_DB_HOST=postgres
|
|
- JAVA_TIMEZONE=Asia/Seoul
|
|
volumes:
|
|
- /opt/metabase/plugins:/plugins
|
|
depends_on:
|
|
- postgres
|
|
networks:
|
|
- metabase-network
|
|
|
|
postgres:
|
|
image: postgres:15-alpine
|
|
container_name: metabase-postgres
|
|
restart: always
|
|
environment:
|
|
- POSTGRES_USER=${POSTGRES_USER}
|
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
|
- POSTGRES_DB=${POSTGRES_DB}
|
|
volumes:
|
|
- /opt/metabase/data:/var/lib/postgresql/data
|
|
networks:
|
|
- metabase-network
|
|
|
|
networks:
|
|
metabase-network:
|
|
driver: bridge
|
|
EOF"
|
|
|
|
# Create .env file for reference
|
|
pct exec "$CT_ID" -- bash -c "cat > /opt/metabase/.env << EOF
|
|
METABASE_PORT=${METABASE_PORT}
|
|
POSTGRES_USER=${POSTGRES_USER}
|
|
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
|
POSTGRES_DB=${POSTGRES_DB}
|
|
EOF"
|
|
|
|
# Start Metabase
|
|
info "Starting Metabase (this may take a few minutes)..."
|
|
pct exec "$CT_ID" -- bash -c "cd /opt/metabase && docker compose up -d"
|
|
|
|
info "Waiting for Metabase to initialize (60 seconds)..."
|
|
sleep 60
|
|
|
|
if pct exec "$CT_ID" -- bash -c "docker ps | grep -q metabase"; then
|
|
success "Metabase installed and running"
|
|
else
|
|
warn "Metabase may still be starting, please check status manually"
|
|
pct exec "$CT_ID" -- bash -c "docker logs metabase" || true
|
|
fi
|
|
}
|
|
|
|
create_metabase_service() {
|
|
info "Creating Metabase systemd service..."
|
|
|
|
pct exec "$CT_ID" -- bash -c 'cat > /etc/systemd/system/metabase.service << EOF
|
|
[Unit]
|
|
Description=Metabase Analytics
|
|
Requires=docker.service
|
|
After=docker.service
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
RemainAfterExit=yes
|
|
WorkingDirectory=/opt/metabase
|
|
ExecStart=/usr/bin/docker compose up -d
|
|
ExecStop=/usr/bin/docker compose down
|
|
TimeoutStartSec=120
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF'
|
|
|
|
pct exec "$CT_ID" -- bash -c "systemctl daemon-reload"
|
|
pct exec "$CT_ID" -- bash -c "systemctl enable metabase"
|
|
|
|
success "Metabase service created"
|
|
}
|
|
|
|
#################################################################
|
|
# Container Notes
|
|
#################################################################
|
|
|
|
add_container_notes() {
|
|
info "Adding container notes with access information..."
|
|
|
|
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="Metabase - BI & Data Analytics Platform
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
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
|
|
|
|
METABASE ACCESS
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
Web UI: http://${container_ip}:${METABASE_PORT}
|
|
|
|
First time setup: Create admin account on first visit
|
|
|
|
DATABASE CREDENTIALS (PostgreSQL)
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
User: ${POSTGRES_USER}
|
|
Password: ${POSTGRES_PASSWORD}
|
|
Database: ${POSTGRES_DB}
|
|
|
|
DOCKER COMMANDS
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
Status: pct exec ${CT_ID} -- docker compose -f /opt/metabase/docker-compose.yml ps
|
|
Logs: pct exec ${CT_ID} -- docker compose -f /opt/metabase/docker-compose.yml logs -f
|
|
Restart: pct exec ${CT_ID} -- docker compose -f /opt/metabase/docker-compose.yml restart
|
|
Update: pct exec ${CT_ID} -- bash -c 'cd /opt/metabase && docker compose pull && docker compose up -d'
|
|
|
|
CONTAINER MANAGEMENT
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
Enter: pct enter ${CT_ID}
|
|
Start: pct start ${CT_ID}
|
|
Stop: pct stop ${CT_ID}
|
|
Restart: pct restart ${CT_ID}
|
|
Delete: pct destroy ${CT_ID}
|
|
|
|
DATA DIRECTORY
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
PostgreSQL Data: /opt/metabase/data
|
|
Plugins: /opt/metabase/plugins
|
|
Config: /opt/metabase/.env
|
|
|
|
AUTO-GENERATED BY: proxmox-lxc-metabase.sh"
|
|
|
|
if pct set "$CT_ID" -description "$notes" 2>/dev/null; then
|
|
success "Container notes added successfully"
|
|
else
|
|
warn "Failed to add container notes (not critical)"
|
|
fi
|
|
}
|
|
|
|
#################################################################
|
|
# Information Display Functions
|
|
#################################################################
|
|
|
|
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 "Metabase 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 " - CPU Cores: $CT_CORES"
|
|
echo " - Memory: ${CT_MEMORY}MB"
|
|
echo " - Disk Size: ${CT_DISK_SIZE}GB"
|
|
echo ""
|
|
echo "Metabase Access:"
|
|
echo " - Web UI: http://${container_ip}:${METABASE_PORT}"
|
|
echo ""
|
|
echo "PostgreSQL Database:"
|
|
echo " - User: ${POSTGRES_USER}"
|
|
echo " - Password: ${POSTGRES_PASSWORD}"
|
|
echo " - Database: ${POSTGRES_DB}"
|
|
echo ""
|
|
info "First time setup: Create your admin account on first visit"
|
|
echo ""
|
|
echo "Docker Commands:"
|
|
echo " - Status: pct exec $CT_ID -- docker compose -f /opt/metabase/docker-compose.yml ps"
|
|
echo " - Logs: pct exec $CT_ID -- docker compose -f /opt/metabase/docker-compose.yml logs -f"
|
|
echo ""
|
|
echo "================================================================="
|
|
}
|
|
|
|
#################################################################
|
|
# Main Execution
|
|
#################################################################
|
|
|
|
main() {
|
|
info "Starting Metabase LXC container creation..."
|
|
echo ""
|
|
|
|
check_root
|
|
check_proxmox
|
|
check_container_exists
|
|
detect_and_download_template
|
|
|
|
create_container
|
|
start_container
|
|
configure_autologin
|
|
|
|
install_docker
|
|
install_metabase
|
|
create_metabase_service
|
|
|
|
add_container_notes
|
|
|
|
display_info
|
|
}
|
|
|
|
main "$@"
|