ProxmoxVED/tools/pve/oci-deploy.sh
2026-01-06 12:36:02 +01:00

353 lines
11 KiB
Bash
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://www.proxmox.com/
function header_info {
clear
cat <<"EOF"
____ ________ ______ __ _
/ __ \/ ____/ / / ____/___ ____ / /_____ _(_)___ ___ _____
/ / / / / / / / / / __ \/ __ \/ __/ __ `/ / __ \/ _ \/ ___/
/ /_/ / /___/ / / /___/ /_/ / / / / /_/ /_/ / / / / / __/ /
\____/\____/_/ \____/\____/_/ /_/\__/\__,_/_/_/ /_/\___/_/
EOF
}
YW=$(echo "\033[33m")
GN=$(echo "\033[1;92m")
RD=$(echo "\033[01;31m")
BL=$(echo "\033[36m")
CL=$(echo "\033[m")
CM="${GN}✔️${CL}"
CROSS="${RD}✖️${CL}"
INFO="${BL}${CL}"
APP="OCI-Container"
header_info
function msg_info() {
local msg="$1"
echo -e "${INFO} ${YW}${msg}...${CL}"
}
function msg_ok() {
local msg="$1"
echo -e "${CM} ${GN}${msg}${CL}"
}
function msg_error() {
local msg="$1"
echo -e "${CROSS} ${RD}${msg}${CL}"
}
# Check Proxmox version
if ! command -v pveversion &>/dev/null; then
msg_error "This script must be run on Proxmox VE"
exit 1
fi
PVE_VER=$(pveversion | grep -oP 'pve-manager/\K[0-9.]+' | cut -d. -f1,2)
MAJOR=$(echo "$PVE_VER" | cut -d. -f1)
MINOR=$(echo "$PVE_VER" | cut -d. -f2)
if [[ "$MAJOR" -lt 9 ]] || { [[ "$MAJOR" -eq 9 ]] && [[ "$MINOR" -lt 1 ]]; }; then
msg_error "Proxmox VE 9.1+ required (current: $PVE_VER)"
exit 1
fi
msg_ok "Proxmox VE $PVE_VER detected"
# Parse OCI image
parse_image() {
local input="$1"
if [[ "$input" =~ ^([^/]+\.[^/]+)/ ]]; then
echo "$input"
elif [[ "$input" =~ / ]]; then
echo "docker.io/$input"
else
echo "docker.io/library/$input"
fi
}
# Interactive image selection
if [[ -z "${OCI_IMAGE:-}" ]]; then
echo ""
echo -e "${YW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${CL}"
echo -e "${BL}Select OCI Image:${CL}"
echo -e "${YW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${CL}"
echo -e " ${BL}1)${CL} nginx:alpine - Lightweight web server"
echo -e " ${BL}2)${CL} postgres:16-alpine - PostgreSQL database"
echo -e " ${BL}3)${CL} redis:alpine - Redis cache"
echo -e " ${BL}4)${CL} mariadb:latest - MariaDB database"
echo -e " ${BL}5)${CL} ghcr.io/linkwarden/linkwarden:latest"
echo -e " ${BL}6)${CL} Custom image"
echo -e "${YW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${CL}"
echo ""
read -r -p "Select option (1-6): " IMAGE_CHOICE
case $IMAGE_CHOICE in
1) OCI_IMAGE="nginx:alpine" ;;
2) OCI_IMAGE="postgres:16-alpine" ;;
3) OCI_IMAGE="redis:alpine" ;;
4) OCI_IMAGE="mariadb:latest" ;;
5) OCI_IMAGE="ghcr.io/linkwarden/linkwarden:latest" ;;
6)
read -r -p "Enter OCI image (e.g., ghcr.io/user/repo:tag): " OCI_IMAGE
[[ -z "$OCI_IMAGE" ]] && {
msg_error "No image specified"
exit 1
}
;;
*)
msg_error "Invalid choice"
exit 1
;;
esac
fi
FULL_IMAGE=$(parse_image "$OCI_IMAGE")
msg_ok "Selected: $FULL_IMAGE"
# Derive container name
if [[ -z "${CT_NAME:-}" ]]; then
DEFAULT_NAME=$(echo "$OCI_IMAGE" | sed 's|.*/||; s/:.*//; s/[^a-zA-Z0-9-]/-/g' | cut -c1-60)
read -r -p "Container name [${DEFAULT_NAME}]: " CT_NAME
CT_NAME=${CT_NAME:-$DEFAULT_NAME}
fi
# Get next VMID
if [[ -z "${VMID:-}" ]]; then
NEXT_ID=$(pvesh get /cluster/nextid)
read -r -p "Container ID [${NEXT_ID}]: " VMID
VMID=${VMID:-$NEXT_ID}
fi
# Resources
if [[ -z "${CORES:-}" ]]; then
read -r -p "CPU cores [2]: " CORES
CORES=${CORES:-2}
fi
if [[ -z "${MEMORY:-}" ]]; then
read -r -p "Memory in MB [2048]: " MEMORY
MEMORY=${MEMORY:-2048}
fi
if [[ -z "${DISK:-}" ]]; then
read -r -p "Disk size in GB [8]: " DISK
DISK=${DISK:-8}
fi
# Storage
if [[ -z "${STORAGE:-}" ]]; then
AVAIL_STORAGE=$(pvesm status | awk '/^local-(zfs|lvm)/ {print $1; exit}')
[[ -z "$AVAIL_STORAGE" ]] && AVAIL_STORAGE="local"
read -r -p "Storage [${AVAIL_STORAGE}]: " STORAGE
STORAGE=${STORAGE:-$AVAIL_STORAGE}
fi
# Network
if [[ -z "${BRIDGE:-}" ]]; then
read -r -p "Network bridge [vmbr0]: " BRIDGE
BRIDGE=${BRIDGE:-vmbr0}
fi
if [[ -z "${IP_MODE:-}" ]]; then
read -r -p "IP mode (dhcp/static) [dhcp]: " IP_MODE
IP_MODE=${IP_MODE:-dhcp}
fi
if [[ "$IP_MODE" == "static" ]]; then
read -r -p "Static IP (CIDR, e.g., 192.168.1.100/24): " STATIC_IP
read -r -p "Gateway IP: " GATEWAY
fi
# Environment variables
declare -a ENV_VARS=()
case "$OCI_IMAGE" in
postgres* | postgresql*)
echo ""
msg_info "PostgreSQL requires environment variables"
read -r -p "PostgreSQL password: " -s PG_PASS
echo ""
ENV_VARS+=("POSTGRES_PASSWORD=$PG_PASS")
read -r -p "Create database (optional): " PG_DB
[[ -n "$PG_DB" ]] && ENV_VARS+=("POSTGRES_DB=$PG_DB")
read -r -p "PostgreSQL user (optional): " PG_USER
[[ -n "$PG_USER" ]] && ENV_VARS+=("POSTGRES_USER=$PG_USER")
;;
mariadb* | mysql*)
echo ""
msg_info "MariaDB/MySQL requires environment variables"
read -r -p "Root password: " -s MYSQL_PASS
echo ""
ENV_VARS+=("MYSQL_ROOT_PASSWORD=$MYSQL_PASS")
read -r -p "Create database (optional): " MYSQL_DB
[[ -n "$MYSQL_DB" ]] && ENV_VARS+=("MYSQL_DATABASE=$MYSQL_DB")
read -r -p "Create user (optional): " MYSQL_USER
if [[ -n "$MYSQL_USER" ]]; then
ENV_VARS+=("MYSQL_USER=$MYSQL_USER")
read -r -p "User password: " -s MYSQL_USER_PASS
echo ""
ENV_VARS+=("MYSQL_PASSWORD=$MYSQL_USER_PASS")
fi
;;
*linkwarden*)
echo ""
msg_info "Linkwarden configuration"
read -r -p "NEXTAUTH_SECRET (press Enter to generate): " NEXTAUTH_SECRET
if [[ -z "$NEXTAUTH_SECRET" ]]; then
NEXTAUTH_SECRET=$(openssl rand -base64 32)
fi
ENV_VARS+=("NEXTAUTH_SECRET=$NEXTAUTH_SECRET")
read -r -p "NEXTAUTH_URL [http://localhost:3000]: " NEXTAUTH_URL
NEXTAUTH_URL=${NEXTAUTH_URL:-http://localhost:3000}
ENV_VARS+=("NEXTAUTH_URL=$NEXTAUTH_URL")
read -r -p "DATABASE_URL (PostgreSQL connection string): " DATABASE_URL
[[ -n "$DATABASE_URL" ]] && ENV_VARS+=("DATABASE_URL=$DATABASE_URL")
;;
esac
# Additional env vars
read -r -p "Add custom environment variables? (y/N): " ADD_ENV
if [[ "${ADD_ENV,,}" =~ ^(y|yes)$ ]]; then
while true; do
read -r -p "Enter KEY=VALUE (or press Enter to finish): " CUSTOM_ENV
[[ -z "$CUSTOM_ENV" ]] && break
ENV_VARS+=("$CUSTOM_ENV")
done
fi
# Privileged mode
read -r -p "Run as privileged container? (y/N): " PRIV_MODE
if [[ "${PRIV_MODE,,}" =~ ^(y|yes)$ ]]; then
UNPRIVILEGED="0"
else
UNPRIVILEGED="1"
fi
# Auto-start
read -r -p "Start container after creation? (Y/n): " AUTO_START
if [[ "${AUTO_START,,}" =~ ^(n|no)$ ]]; then
START_AFTER="no"
else
START_AFTER="yes"
fi
# Summary
echo ""
echo -e "${YW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${CL}"
echo -e "${BL}Container Configuration Summary:${CL}"
echo -e "${YW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${CL}"
echo -e " Image: $FULL_IMAGE"
echo -e " ID: $VMID"
echo -e " Name: $CT_NAME"
echo -e " CPUs: $CORES"
echo -e " Memory: ${MEMORY}MB"
echo -e " Disk: ${DISK}GB"
echo -e " Storage: $STORAGE"
echo -e " Network: $BRIDGE ($IP_MODE)"
[[ ${#ENV_VARS[@]} -gt 0 ]] && echo -e " Env vars: ${#ENV_VARS[@]} configured"
echo -e "${YW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${CL}"
echo ""
read -r -p "Proceed with creation? (Y/n): " CONFIRM
if [[ "${CONFIRM,,}" =~ ^(n|no)$ ]]; then
msg_error "Cancelled by user"
exit 0
fi
# Create container
msg_info "Creating container $VMID"
# Build pct create command
PCT_CMD="pct create $VMID"
PCT_CMD+=" --hostname $CT_NAME"
PCT_CMD+=" --cores $CORES"
PCT_CMD+=" --memory $MEMORY"
PCT_CMD+=" --rootfs ${STORAGE}:${DISK},oci=${FULL_IMAGE}"
PCT_CMD+=" --unprivileged $UNPRIVILEGED"
if [[ "$IP_MODE" == "static" && -n "$STATIC_IP" ]]; then
PCT_CMD+=" --net0 name=eth0,bridge=$BRIDGE,ip=$STATIC_IP"
[[ -n "$GATEWAY" ]] && PCT_CMD+=",gw=$GATEWAY"
else
PCT_CMD+=" --net0 name=eth0,bridge=$BRIDGE,ip=dhcp"
fi
if eval "$PCT_CMD" 2>&1; then
msg_ok "Container created"
else
msg_error "Failed to create container"
exit 1
fi
# Set environment variables
if [[ ${#ENV_VARS[@]} -gt 0 ]]; then
msg_info "Configuring environment variables"
for env_var in "${ENV_VARS[@]}"; do
if pct set "$VMID" -env "$env_var" &>/dev/null; then
:
else
msg_error "Failed to set: $env_var"
fi
done
msg_ok "Environment variables configured (${#ENV_VARS[@]} variables)"
fi
# Start container
if [[ "$START_AFTER" == "yes" ]]; then
msg_info "Starting container"
if pct start "$VMID" 2>&1; then
msg_ok "Container started"
# Wait for network
sleep 3
CT_IP=$(pct exec "$VMID" -- hostname -I 2>/dev/null | awk '{print $1}' || echo "N/A")
echo ""
echo -e "${GN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${CL}"
echo -e "${BL}Container Information:${CL}"
echo -e "${GN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${CL}"
echo -e " ID: ${GN}$VMID${CL}"
echo -e " Name: ${GN}$CT_NAME${CL}"
echo -e " Image: ${GN}$FULL_IMAGE${CL}"
echo -e " IP: ${GN}$CT_IP${CL}"
echo -e " Status: ${GN}Running${CL}"
echo -e "${GN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${CL}"
echo ""
echo -e "${INFO} ${YW}Access console:${CL} pct console $VMID"
echo -e "${INFO} ${YW}View logs:${CL} pct logs $VMID"
echo ""
else
msg_error "Failed to start container"
fi
else
echo ""
echo -e "${GN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${CL}"
echo -e "${BL}Container Information:${CL}"
echo -e "${GN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${CL}"
echo -e " ID: ${GN}$VMID${CL}"
echo -e " Name: ${GN}$CT_NAME${CL}"
echo -e " Image: ${GN}$FULL_IMAGE${CL}"
echo -e " Status: ${YW}Stopped${CL}"
echo -e "${GN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${CL}"
echo ""
echo -e "${INFO} ${YW}Start with:${CL} pct start $VMID"
echo ""
fi