Keycloak: - start-dev 모드로 변경 (--optimized 제거) - KC_BOOTSTRAP_ADMIN 환경변수 사용 (deprecated 경고 해결) - 데이터 디렉토리 권한 설정 추가 Vaultwarden: - docker-compose.yml 환경변수 형식 수정 - 간결화된 스크립트 구조 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
254 lines
8.8 KiB
Bash
254 lines
8.8 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
# Vaultwarden LXC Installation Script
|
|
# Description: Bitwarden-compatible password manager server
|
|
# OS: Debian 12 (Bookworm) - Auto-detected latest version
|
|
# Ports: Web UI: 8080, WebSocket: 3012
|
|
# Repository: https://github.com/dani-garcia/vaultwarden
|
|
# Last Updated: 2026-01-05
|
|
|
|
set -euo pipefail
|
|
|
|
#################################################################
|
|
# Configuration Variables
|
|
#################################################################
|
|
|
|
CT_ID=${CT_ID:-21002}
|
|
CT_HOSTNAME=${CT_HOSTNAME:-"vaultwarden"}
|
|
CT_CORES=${CT_CORES:-1}
|
|
CT_MEMORY=${CT_MEMORY:-1024}
|
|
CT_SWAP=${CT_SWAP:-512}
|
|
CT_DISK_SIZE=${CT_DISK_SIZE:-10}
|
|
|
|
CT_IP=${CT_IP:-"dhcp"}
|
|
CT_GATEWAY=${CT_GATEWAY:-""}
|
|
CT_BRIDGE=${CT_BRIDGE:-"vmbr0"}
|
|
CT_NAMESERVER=${CT_NAMESERVER:-"8.8.8.8"}
|
|
|
|
CT_STORAGE=${CT_STORAGE:-"local-lvm"}
|
|
TEMPLATE_STORAGE=${TEMPLATE_STORAGE:-"local"}
|
|
DEBIAN_VERSION="12"
|
|
TEMPLATE_NAME=""
|
|
|
|
VW_PORT=${VW_PORT:-8080}
|
|
VW_WEBSOCKET_PORT=${VW_WEBSOCKET_PORT:-3012}
|
|
VW_ADMIN_TOKEN=${VW_ADMIN_TOKEN:-""}
|
|
VW_SIGNUPS_ALLOWED=${VW_SIGNUPS_ALLOWED:-"true"}
|
|
VW_DOMAIN=${VW_DOMAIN:-""}
|
|
|
|
CT_ONBOOT=${CT_ONBOOT:-1}
|
|
CT_UNPRIVILEGED=${CT_UNPRIVILEGED:-0}
|
|
CT_FEATURES=${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"; }
|
|
|
|
#################################################################
|
|
# Functions
|
|
#################################################################
|
|
|
|
check_root() {
|
|
[[ $EUID -ne 0 ]] && error "This script must be run as root" && exit 1
|
|
}
|
|
|
|
check_proxmox() {
|
|
command -v pct &> /dev/null || { error "Run on Proxmox VE host"; exit 1; }
|
|
}
|
|
|
|
check_container_exists() {
|
|
pct status "$CT_ID" &> /dev/null && { error "Container $CT_ID exists"; exit 1; }
|
|
}
|
|
|
|
generate_admin_token() {
|
|
if [[ -z "$VW_ADMIN_TOKEN" ]]; then
|
|
VW_ADMIN_TOKEN=$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c 32)
|
|
info "Generated admin token: $VW_ADMIN_TOKEN"
|
|
fi
|
|
}
|
|
|
|
detect_and_download_template() {
|
|
info "Updating template database..."
|
|
pveam update 2>&1 || true
|
|
|
|
local available_template
|
|
available_template=$(pveam available --section system 2>/dev/null | grep "debian-${DEBIAN_VERSION}" | grep "standard" | tail -1 | awk '{print $2}')
|
|
[[ -z "$available_template" ]] && error "No Debian ${DEBIAN_VERSION} template found" && exit 1
|
|
|
|
TEMPLATE_NAME="$available_template"
|
|
info "Found template: $TEMPLATE_NAME"
|
|
|
|
pveam list "$TEMPLATE_STORAGE" 2>/dev/null | grep -q "$TEMPLATE_NAME" || pveam download "$TEMPLATE_STORAGE" "$TEMPLATE_NAME" 2>&1
|
|
success "Template ready"
|
|
}
|
|
|
|
create_container() {
|
|
info "Creating LXC container $CT_ID ($CT_HOSTNAME)..."
|
|
local net_config="name=eth0,bridge=${CT_BRIDGE},ip=${CT_IP}"
|
|
[[ "$CT_IP" != "dhcp" ]] && [[ -n "$CT_GATEWAY" ]] && net_config="${net_config},gw=${CT_GATEWAY}"
|
|
|
|
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 || exit 1
|
|
success "Container created"
|
|
}
|
|
|
|
start_container() {
|
|
info "Starting container..."
|
|
pct start "$CT_ID" && sleep 5
|
|
success "Container started"
|
|
}
|
|
|
|
install_docker() {
|
|
info "Installing Docker..."
|
|
pct exec "$CT_ID" -- bash -c "apt-get update -qq"
|
|
pct exec "$CT_ID" -- bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq ca-certificates curl gnupg lsb-release"
|
|
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"
|
|
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'
|
|
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 && systemctl start docker"
|
|
sleep 3
|
|
success "Docker installed"
|
|
}
|
|
|
|
install_vaultwarden() {
|
|
info "Installing Vaultwarden..."
|
|
pct exec "$CT_ID" -- bash -c "mkdir -p /opt/vaultwarden/data && chmod -R 777 /opt/vaultwarden/data"
|
|
|
|
pct exec "$CT_ID" -- bash -c "cat > /opt/vaultwarden/docker-compose.yml << 'EOF'
|
|
services:
|
|
vaultwarden:
|
|
image: vaultwarden/server:latest
|
|
container_name: vaultwarden
|
|
restart: always
|
|
environment:
|
|
SIGNUPS_ALLOWED: \"${VW_SIGNUPS_ALLOWED}\"
|
|
INVITATIONS_ALLOWED: \"true\"
|
|
WEBSOCKET_ENABLED: \"true\"
|
|
ADMIN_TOKEN: \"${VW_ADMIN_TOKEN}\"
|
|
DOMAIN: \"${VW_DOMAIN}\"
|
|
TZ: \"Asia/Seoul\"
|
|
ports:
|
|
- \"${VW_PORT}:80\"
|
|
- \"${VW_WEBSOCKET_PORT}:3012\"
|
|
volumes:
|
|
- ./data:/data
|
|
healthcheck:
|
|
test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:80/alive\"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
EOF"
|
|
|
|
pct exec "$CT_ID" -- bash -c "cd /opt/vaultwarden && docker compose up -d"
|
|
|
|
info "Waiting for Vaultwarden to start..."
|
|
local max_attempts=20
|
|
local attempt=1
|
|
while [[ $attempt -le $max_attempts ]]; do
|
|
if pct exec "$CT_ID" -- bash -c "curl -s -o /dev/null -w '%{http_code}' http://localhost:${VW_PORT}/" | grep -q "200"; then
|
|
success "Vaultwarden is running!"
|
|
break
|
|
fi
|
|
echo -n "."
|
|
sleep 3
|
|
((attempt++))
|
|
done
|
|
echo ""
|
|
success "Vaultwarden installed"
|
|
}
|
|
|
|
create_vaultwarden_service() {
|
|
pct exec "$CT_ID" -- bash -c 'cat > /etc/systemd/system/vaultwarden.service << EOF
|
|
[Unit]
|
|
Description=Vaultwarden Password Manager
|
|
Requires=docker.service
|
|
After=docker.service
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
RemainAfterExit=yes
|
|
WorkingDirectory=/opt/vaultwarden
|
|
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 && systemctl enable vaultwarden"
|
|
success "Vaultwarden service created"
|
|
}
|
|
|
|
create_backup_script() {
|
|
pct exec "$CT_ID" -- bash -c 'cat > /opt/vaultwarden/backup.sh << EOF
|
|
#!/bin/bash
|
|
BACKUP_DIR="/opt/vaultwarden/backups"
|
|
DATA_DIR="/opt/vaultwarden/data"
|
|
DATE=\$(date +%Y%m%d_%H%M%S)
|
|
mkdir -p "\$BACKUP_DIR"
|
|
tar -czf "\$BACKUP_DIR/vaultwarden_\$DATE.tar.gz" -C "\$DATA_DIR" .
|
|
ls -tp "\$BACKUP_DIR"/vaultwarden_*.tar.gz | tail -n +8 | xargs -r rm --
|
|
echo "Backup: \$BACKUP_DIR/vaultwarden_\$DATE.tar.gz"
|
|
EOF'
|
|
pct exec "$CT_ID" -- bash -c "chmod +x /opt/vaultwarden/backup.sh"
|
|
pct exec "$CT_ID" -- bash -c 'echo "0 3 * * * /opt/vaultwarden/backup.sh >> /var/log/vaultwarden-backup.log 2>&1" | crontab -'
|
|
success "Backup script created"
|
|
}
|
|
|
|
verify_installation() {
|
|
local container_ip
|
|
container_ip=$(pct exec "$CT_ID" -- bash -c "hostname -I | awk '{print \$1}'" 2>/dev/null || echo "unknown")
|
|
|
|
echo ""
|
|
echo "================================================================"
|
|
echo -e "${GREEN}Vaultwarden Installation Complete!${NC}"
|
|
echo "================================================================"
|
|
echo "Container ID: $CT_ID | Hostname: $CT_HOSTNAME | IP: $container_ip"
|
|
echo ""
|
|
echo "Web Vault: http://${container_ip}:${VW_PORT}"
|
|
[[ -n "$VW_ADMIN_TOKEN" ]] && echo "Admin Panel: http://${container_ip}:${VW_PORT}/admin"
|
|
[[ -n "$VW_ADMIN_TOKEN" ]] && echo "Admin Token: ${VW_ADMIN_TOKEN}"
|
|
echo ""
|
|
echo "Commands:"
|
|
echo " pct exec $CT_ID -- docker compose -f /opt/vaultwarden/docker-compose.yml logs -f"
|
|
echo " pct exec $CT_ID -- /opt/vaultwarden/backup.sh"
|
|
echo "================================================================"
|
|
}
|
|
|
|
main() {
|
|
echo "================================================================"
|
|
echo "Vaultwarden LXC Installation Script"
|
|
echo "================================================================"
|
|
|
|
check_root
|
|
check_proxmox
|
|
check_container_exists
|
|
generate_admin_token
|
|
detect_and_download_template
|
|
create_container
|
|
start_container
|
|
install_docker
|
|
install_vaultwarden
|
|
create_vaultwarden_service
|
|
create_backup_script
|
|
verify_installation
|
|
}
|
|
|
|
main "$@"
|