init
This commit is contained in:
commit
cd56e73e36
2 files changed
+512
No files matched your search
Executable
+258
@@ -0,0 +1,258 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# docker-opensuse-ct.sh — interactive (whiptail) installer that provisions an
|
||||||
|
# openSUSE LXC container on Proxmox and installs Docker + Docker Compose inside it.
|
||||||
|
#
|
||||||
|
# Same UX pattern as grafana-opensuse-ct.sh: Default/Advanced menu, whiptail
|
||||||
|
# prompts, then a scripted pct create + pct exec install flow.
|
||||||
|
#
|
||||||
|
# Run this ON the Proxmox host as root:
|
||||||
|
# ./docker-opensuse-ct.sh
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
APP="Docker"
|
||||||
|
COMPOSE_FALLBACK_VERSION="v2.29.7"
|
||||||
|
|
||||||
|
# ---------- helpers ----------
|
||||||
|
msg_info() { echo -e "\e[1;34m[*]\e[0m $1"; }
|
||||||
|
msg_ok() { echo -e "\e[1;32m[OK]\e[0m $1"; }
|
||||||
|
msg_err() { echo -e "\e[1;31m[ERR]\e[0m $1" >&2; }
|
||||||
|
|
||||||
|
die() { msg_err "$1"; exit 1; }
|
||||||
|
|
||||||
|
command -v pct >/dev/null 2>&1 || die "pct not found — run this on a Proxmox VE host."
|
||||||
|
command -v whiptail >/dev/null 2>&1 || { msg_info "whiptail not found, installing..."; apt-get update -qq && apt-get install -y whiptail; }
|
||||||
|
|
||||||
|
# ---------- welcome + mode ----------
|
||||||
|
whiptail --title "$APP LXC Installer" --msgbox \
|
||||||
|
"This will create an openSUSE LXC container on this Proxmox host and install Docker + Docker Compose inside it." \
|
||||||
|
10 65
|
||||||
|
|
||||||
|
MODE=$(whiptail --title "$APP LXC Installer" --menu "Choose setup mode" 12 65 2 \
|
||||||
|
"default" "Quick install — sane defaults, minimal prompts" \
|
||||||
|
"advanced" "Advanced — customize every setting" \
|
||||||
|
3>&1 1>&2 2>&3) || exit 1
|
||||||
|
|
||||||
|
# ---------- gather settings ----------
|
||||||
|
NEXTID=$(pvesh get /cluster/nextid)
|
||||||
|
|
||||||
|
if [[ "$MODE" == "default" ]]; then
|
||||||
|
CTID="$NEXTID"
|
||||||
|
CT_HOSTNAME="docker"
|
||||||
|
CT_STORAGE=$(pvesm status -content rootdir | awk 'NR==2{print $1}')
|
||||||
|
[[ -n "$CT_STORAGE" ]] || die "No rootdir-capable storage found."
|
||||||
|
DISK_GB=16
|
||||||
|
CORES=2
|
||||||
|
MEMORY=2048
|
||||||
|
SWAP=512
|
||||||
|
BRIDGE="vmbr0"
|
||||||
|
VLAN=""
|
||||||
|
NET_MODE="dhcp"
|
||||||
|
UNPRIVILEGED=1
|
||||||
|
ROOT_PASSWORD=$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 20)
|
||||||
|
SSH_PUBKEY_FILE=""
|
||||||
|
else
|
||||||
|
CTID=$(whiptail --inputbox "Container ID" 8 58 "$NEXTID" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
CT_HOSTNAME=$(whiptail --inputbox "Hostname" 8 58 "docker" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
|
||||||
|
mapfile -t STORAGES < <(pvesm status -content rootdir | awk 'NR>1{print $1}')
|
||||||
|
[[ ${#STORAGES[@]} -gt 0 ]] || die "No rootdir-capable storage found."
|
||||||
|
RADIO_ARGS=(); first=1
|
||||||
|
for s in "${STORAGES[@]}"; do
|
||||||
|
if [[ $first -eq 1 ]]; then RADIO_ARGS+=("$s" "" ON); first=0; else RADIO_ARGS+=("$s" "" OFF); fi
|
||||||
|
done
|
||||||
|
CT_STORAGE=$(whiptail --radiolist "Storage for rootfs" 15 60 6 "${RADIO_ARGS[@]}" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
|
||||||
|
DISK_GB=$(whiptail --inputbox "Disk size (GB) — Docker images/volumes add up, size generously" 8 70 "16" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
CORES=$(whiptail --inputbox "CPU cores" 8 58 "2" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
MEMORY=$(whiptail --inputbox "Memory (MB)" 8 58 "2048" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
SWAP=$(whiptail --inputbox "Swap (MB)" 8 58 "512" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
|
||||||
|
mapfile -t BRIDGES < <(ip -o link show type bridge | awk -F': ' '{print $2}')
|
||||||
|
[[ ${#BRIDGES[@]} -gt 0 ]] || die "No bridges found on this host."
|
||||||
|
RADIO_ARGS=(); first=1
|
||||||
|
for b in "${BRIDGES[@]}"; do
|
||||||
|
if [[ $first -eq 1 ]]; then RADIO_ARGS+=("$b" "" ON); first=0; else RADIO_ARGS+=("$b" "" OFF); fi
|
||||||
|
done
|
||||||
|
BRIDGE=$(whiptail --radiolist "Network bridge" 15 60 6 "${RADIO_ARGS[@]}" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
|
||||||
|
VLAN=$(whiptail --inputbox "VLAN tag (blank = untagged)" 8 58 "" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
|
||||||
|
NET_MODE=$(whiptail --menu "Network mode" 12 58 2 \
|
||||||
|
"dhcp" "DHCP" \
|
||||||
|
"static" "Static IP" \
|
||||||
|
--title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
|
||||||
|
if [[ "$NET_MODE" == "static" ]]; then
|
||||||
|
STATIC_IP_CIDR=$(whiptail --inputbox "IP/CIDR, e.g. 192.168.10.60/24" 8 58 "" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
STATIC_GW=$(whiptail --inputbox "Gateway" 8 58 "" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if whiptail --title "$APP LXC" --yesno \
|
||||||
|
"Create as unprivileged container?\n\nRecommended, but Docker needs the nesting+keyctl features either way (already enabled by this script). Choose 'No' only if you hit cgroup/overlay issues under unprivileged mode." 12 70; then
|
||||||
|
UNPRIVILEGED=1
|
||||||
|
else
|
||||||
|
UNPRIVILEGED=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
ROOT_PASSWORD=$(whiptail --passwordbox "Root password for the CT" 8 58 --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
SSH_PUBKEY_FILE=$(whiptail --inputbox "SSH public key file to inject (blank = skip)" 8 58 "$HOME/.ssh/id_rsa.pub" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
pct status "$CTID" >/dev/null 2>&1 && die "CT $CTID already exists."
|
||||||
|
|
||||||
|
# ---------- template ----------
|
||||||
|
mapfile -t TEMPLATES < <(pveam available -section system | awk '{print $2}' | grep -i opensuse || true)
|
||||||
|
if [[ ${#TEMPLATES[@]} -eq 0 ]]; then
|
||||||
|
msg_info "Refreshing template index..."
|
||||||
|
pveam update
|
||||||
|
mapfile -t TEMPLATES < <(pveam available -section system | awk '{print $2}' | grep -i opensuse || true)
|
||||||
|
fi
|
||||||
|
[[ ${#TEMPLATES[@]} -gt 0 ]] || die "No openSUSE templates found in the Proxmox appliance list."
|
||||||
|
|
||||||
|
if [[ "$MODE" == "default" ]]; then
|
||||||
|
TEMPLATE="${TEMPLATES[-1]}" # newest listed
|
||||||
|
else
|
||||||
|
RADIO_ARGS=(); first=1
|
||||||
|
for t in "${TEMPLATES[@]}"; do
|
||||||
|
if [[ $first -eq 1 ]]; then RADIO_ARGS+=("$t" "" ON); first=0; else RADIO_ARGS+=("$t" "" OFF); fi
|
||||||
|
done
|
||||||
|
TEMPLATE=$(whiptail --radiolist "Choose openSUSE template" 20 72 10 "${RADIO_ARGS[@]}" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! pveam list local | grep -q "$TEMPLATE"; then
|
||||||
|
msg_info "Downloading template $TEMPLATE..."
|
||||||
|
pveam download local "$TEMPLATE"
|
||||||
|
msg_ok "Template downloaded."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------- confirm ----------
|
||||||
|
SUMMARY="CTID: $CTID
|
||||||
|
Hostname: $CT_HOSTNAME
|
||||||
|
Template: $TEMPLATE
|
||||||
|
Storage: $CT_STORAGE (${DISK_GB}G)
|
||||||
|
CPU/RAM: ${CORES} cores / ${MEMORY}MB (+${SWAP}MB swap)
|
||||||
|
Bridge: $BRIDGE${VLAN:+ (vlan $VLAN)}
|
||||||
|
Network: ${NET_MODE}${STATIC_IP_CIDR:+ $STATIC_IP_CIDR via $STATIC_GW}
|
||||||
|
Priv: $([[ $UNPRIVILEGED -eq 1 ]] && echo unprivileged || echo privileged)
|
||||||
|
Features: nesting=1,keyctl=1 (required for Docker)"
|
||||||
|
|
||||||
|
whiptail --title "Confirm" --yesno "$SUMMARY
|
||||||
|
|
||||||
|
Proceed?" 20 65 || exit 1
|
||||||
|
|
||||||
|
# ---------- build net string ----------
|
||||||
|
if [[ "$NET_MODE" == "static" ]]; then
|
||||||
|
NET_STR="name=eth0,bridge=${BRIDGE},ip=${STATIC_IP_CIDR},gw=${STATIC_GW}"
|
||||||
|
else
|
||||||
|
NET_STR="name=eth0,bridge=${BRIDGE},ip=dhcp"
|
||||||
|
fi
|
||||||
|
[[ -n "$VLAN" ]] && NET_STR="${NET_STR},tag=${VLAN}"
|
||||||
|
|
||||||
|
# ---------- create + start ----------
|
||||||
|
CREATE_ARGS=(
|
||||||
|
"$CTID" "local:vztmpl/${TEMPLATE}"
|
||||||
|
--hostname "$CT_HOSTNAME"
|
||||||
|
--cores "$CORES"
|
||||||
|
--memory "$MEMORY"
|
||||||
|
--swap "$SWAP"
|
||||||
|
--rootfs "${CT_STORAGE}:${DISK_GB}"
|
||||||
|
--net0 "$NET_STR"
|
||||||
|
--unprivileged "$UNPRIVILEGED"
|
||||||
|
--onboot 1
|
||||||
|
--features "nesting=1,keyctl=1"
|
||||||
|
--password "$ROOT_PASSWORD"
|
||||||
|
)
|
||||||
|
[[ -n "${SSH_PUBKEY_FILE:-}" && -f "${SSH_PUBKEY_FILE:-}" ]] && CREATE_ARGS+=(--ssh-public-keys "$SSH_PUBKEY_FILE")
|
||||||
|
|
||||||
|
msg_info "Creating CT $CTID ($CT_HOSTNAME)..."
|
||||||
|
pct create "${CREATE_ARGS[@]}" >/dev/null
|
||||||
|
msg_ok "Container created."
|
||||||
|
|
||||||
|
msg_info "Starting CT $CTID..."
|
||||||
|
pct start "$CTID"
|
||||||
|
|
||||||
|
msg_info "Waiting for network..."
|
||||||
|
for i in {1..30}; do
|
||||||
|
pct exec "$CTID" -- sh -c "true" >/dev/null 2>&1 && pct exec "$CTID" -- ping -c1 -W2 1.1.1.1 >/dev/null 2>&1 && break
|
||||||
|
sleep 2
|
||||||
|
[[ $i -eq 30 ]] && die "CT never came up with network access."
|
||||||
|
done
|
||||||
|
msg_ok "Network is up."
|
||||||
|
|
||||||
|
# ---------- install docker inside the CT ----------
|
||||||
|
run() { pct exec "$CTID" -- bash -c "$1"; }
|
||||||
|
|
||||||
|
msg_info "Refreshing zypper repos..."
|
||||||
|
run "zypper --non-interactive refresh" >/dev/null
|
||||||
|
msg_ok "Repos refreshed."
|
||||||
|
|
||||||
|
msg_info "Installing prerequisites (curl)..."
|
||||||
|
run "zypper --non-interactive install curl" >/dev/null
|
||||||
|
msg_ok "Prerequisites installed."
|
||||||
|
|
||||||
|
msg_info "Installing Docker from openSUSE repos..."
|
||||||
|
run "zypper --non-interactive install docker" >/dev/null
|
||||||
|
msg_ok "Docker installed."
|
||||||
|
|
||||||
|
msg_info "Enabling and starting the Docker service..."
|
||||||
|
run "systemctl enable --now docker" >/dev/null
|
||||||
|
msg_ok "Docker service running."
|
||||||
|
|
||||||
|
msg_info "Resolving latest Docker Compose release..."
|
||||||
|
COMPOSE_VERSION=$(pct exec "$CTID" -- curl -s https://api.github.com/repos/docker/compose/releases/latest \
|
||||||
|
| grep -Po '"tag_name":\s*"\K[^"]+' || true)
|
||||||
|
[[ -z "$COMPOSE_VERSION" ]] && COMPOSE_VERSION="$COMPOSE_FALLBACK_VERSION"
|
||||||
|
msg_ok "Using Compose $COMPOSE_VERSION"
|
||||||
|
|
||||||
|
msg_info "Installing Docker Compose plugin (${COMPOSE_VERSION})..."
|
||||||
|
run "mkdir -p /usr/local/lib/docker/cli-plugins"
|
||||||
|
run "curl -sSL https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-linux-x86_64 -o /usr/local/lib/docker/cli-plugins/docker-compose"
|
||||||
|
run "chmod +x /usr/local/lib/docker/cli-plugins/docker-compose"
|
||||||
|
msg_ok "Docker Compose plugin installed."
|
||||||
|
|
||||||
|
msg_info "Verifying install (docker run hello-world)..."
|
||||||
|
if run "docker run --rm hello-world" >/dev/null 2>&1; then
|
||||||
|
msg_ok "Docker can run containers."
|
||||||
|
else
|
||||||
|
msg_err "hello-world test failed — likely a cgroup/overlay issue under unprivileged LXC. See summary notes below."
|
||||||
|
fi
|
||||||
|
|
||||||
|
DOCKER_VERSION=$(run "docker --version" || echo "unknown")
|
||||||
|
COMPOSE_VERSION_INSTALLED=$(run "docker compose version" || echo "unknown")
|
||||||
|
|
||||||
|
# ---------- summary ----------
|
||||||
|
CT_IP=$(pct exec "$CTID" -- hostname -I 2>/dev/null | awk '{print $1}')
|
||||||
|
|
||||||
|
whiptail --title "$APP LXC Installer — Done" --msgbox \
|
||||||
|
"Docker is running.
|
||||||
|
|
||||||
|
CT ID: $CTID
|
||||||
|
IP: ${CT_IP}
|
||||||
|
$DOCKER_VERSION
|
||||||
|
$COMPOSE_VERSION_INSTALLED
|
||||||
|
Root pass: ${ROOT_PASSWORD}
|
||||||
|
|
||||||
|
Enter with: pct enter $CTID
|
||||||
|
(also printed below in the terminal)" 18 65
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
========================================
|
||||||
|
$APP LXC ready
|
||||||
|
========================================
|
||||||
|
CT ID: $CTID
|
||||||
|
IP: ${CT_IP}
|
||||||
|
$DOCKER_VERSION
|
||||||
|
$COMPOSE_VERSION_INSTALLED
|
||||||
|
Root pass: ${ROOT_PASSWORD}
|
||||||
|
|
||||||
|
Enter with: pct enter $CTID
|
||||||
|
|
||||||
|
NOTE: if 'docker run hello-world' failed above, this LXC/kernel combo
|
||||||
|
doesn't support unprivileged Docker cleanly — re-run and choose
|
||||||
|
privileged mode in Advanced settings, or set storage-driver=vfs in
|
||||||
|
/etc/docker/daemon.json as a slower fallback.
|
||||||
|
========================================
|
||||||
|
EOF
|
||||||
Executable
+254
@@ -0,0 +1,254 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# grafana-opensuse-ct.sh — interactive (whiptail) installer that provisions an
|
||||||
|
# openSUSE LXC container on Proxmox and installs/configures Grafana inside it.
|
||||||
|
#
|
||||||
|
# Mimics the community-scripts.github.io/ProxmoxVE UX: a Default/Advanced menu,
|
||||||
|
# whiptail prompts, then a scripted pct create + pct exec install flow.
|
||||||
|
#
|
||||||
|
# Run this ON the Proxmox host as root:
|
||||||
|
# ./grafana-opensuse-ct.sh
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
APP="Grafana"
|
||||||
|
|
||||||
|
# ---------- helpers ----------
|
||||||
|
msg_info() { echo -e "\e[1;34m[*]\e[0m $1"; }
|
||||||
|
msg_ok() { echo -e "\e[1;32m[OK]\e[0m $1"; }
|
||||||
|
msg_err() { echo -e "\e[1;31m[ERR]\e[0m $1" >&2; }
|
||||||
|
|
||||||
|
die() { msg_err "$1"; exit 1; }
|
||||||
|
|
||||||
|
command -v pct >/dev/null 2>&1 || die "pct not found — run this on a Proxmox VE host."
|
||||||
|
command -v whiptail >/dev/null 2>&1 || { msg_info "whiptail not found, installing..."; apt-get update -qq && apt-get install -y whiptail; }
|
||||||
|
|
||||||
|
# ---------- welcome + mode ----------
|
||||||
|
whiptail --title "$APP LXC Installer" --msgbox \
|
||||||
|
"This will create an openSUSE LXC container on this Proxmox host and install Grafana inside it." \
|
||||||
|
10 65
|
||||||
|
|
||||||
|
MODE=$(whiptail --title "$APP LXC Installer" --menu "Choose setup mode" 12 65 2 \
|
||||||
|
"default" "Quick install — sane defaults, minimal prompts" \
|
||||||
|
"advanced" "Advanced — customize every setting" \
|
||||||
|
3>&1 1>&2 2>&3) || exit 1
|
||||||
|
|
||||||
|
# ---------- gather settings ----------
|
||||||
|
NEXTID=$(pvesh get /cluster/nextid)
|
||||||
|
|
||||||
|
if [[ "$MODE" == "default" ]]; then
|
||||||
|
CTID="$NEXTID"
|
||||||
|
CT_HOSTNAME="grafana"
|
||||||
|
CT_STORAGE=$(pvesm status -content rootdir | awk 'NR==2{print $1}')
|
||||||
|
[[ -n "$CT_STORAGE" ]] || die "No rootdir-capable storage found."
|
||||||
|
DISK_GB=8
|
||||||
|
CORES=2
|
||||||
|
MEMORY=1024
|
||||||
|
SWAP=512
|
||||||
|
BRIDGE="vmbr0"
|
||||||
|
VLAN=""
|
||||||
|
NET_MODE="dhcp"
|
||||||
|
UNPRIVILEGED=1
|
||||||
|
GRAFANA_PORT=3000
|
||||||
|
ROOT_PASSWORD=$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 20)
|
||||||
|
GRAFANA_ADMIN_PASSWORD=$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 20)
|
||||||
|
SSH_PUBKEY_FILE=""
|
||||||
|
else
|
||||||
|
CTID=$(whiptail --inputbox "Container ID" 8 58 "$NEXTID" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
CT_HOSTNAME=$(whiptail --inputbox "Hostname" 8 58 "grafana" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
|
||||||
|
mapfile -t STORAGES < <(pvesm status -content rootdir | awk 'NR>1{print $1}')
|
||||||
|
[[ ${#STORAGES[@]} -gt 0 ]] || die "No rootdir-capable storage found."
|
||||||
|
RADIO_ARGS=(); first=1
|
||||||
|
for s in "${STORAGES[@]}"; do
|
||||||
|
if [[ $first -eq 1 ]]; then RADIO_ARGS+=("$s" "" ON); first=0; else RADIO_ARGS+=("$s" "" OFF); fi
|
||||||
|
done
|
||||||
|
CT_STORAGE=$(whiptail --radiolist "Storage for rootfs" 15 60 6 "${RADIO_ARGS[@]}" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
|
||||||
|
DISK_GB=$(whiptail --inputbox "Disk size (GB)" 8 58 "8" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
CORES=$(whiptail --inputbox "CPU cores" 8 58 "2" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
MEMORY=$(whiptail --inputbox "Memory (MB)" 8 58 "1024" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
SWAP=$(whiptail --inputbox "Swap (MB)" 8 58 "512" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
|
||||||
|
mapfile -t BRIDGES < <(ip -o link show type bridge | awk -F': ' '{print $2}')
|
||||||
|
[[ ${#BRIDGES[@]} -gt 0 ]] || die "No bridges found on this host."
|
||||||
|
RADIO_ARGS=(); first=1
|
||||||
|
for b in "${BRIDGES[@]}"; do
|
||||||
|
if [[ $first -eq 1 ]]; then RADIO_ARGS+=("$b" "" ON); first=0; else RADIO_ARGS+=("$b" "" OFF); fi
|
||||||
|
done
|
||||||
|
BRIDGE=$(whiptail --radiolist "Network bridge" 15 60 6 "${RADIO_ARGS[@]}" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
|
||||||
|
VLAN=$(whiptail --inputbox "VLAN tag (blank = untagged)" 8 58 "" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
|
||||||
|
NET_MODE=$(whiptail --menu "Network mode" 12 58 2 \
|
||||||
|
"dhcp" "DHCP" \
|
||||||
|
"static" "Static IP" \
|
||||||
|
--title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
|
||||||
|
if [[ "$NET_MODE" == "static" ]]; then
|
||||||
|
STATIC_IP_CIDR=$(whiptail --inputbox "IP/CIDR, e.g. 192.168.10.50/24" 8 58 "" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
STATIC_GW=$(whiptail --inputbox "Gateway" 8 58 "" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if whiptail --title "$APP LXC" --yesno "Create as unprivileged container? (recommended)" 8 58; then
|
||||||
|
UNPRIVILEGED=1
|
||||||
|
else
|
||||||
|
UNPRIVILEGED=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
GRAFANA_PORT=$(whiptail --inputbox "Grafana HTTP port" 8 58 "3000" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
ROOT_PASSWORD=$(whiptail --passwordbox "Root password for the CT" 8 58 --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
GRAFANA_ADMIN_PASSWORD=$(whiptail --passwordbox "Grafana admin password" 8 58 --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
SSH_PUBKEY_FILE=$(whiptail --inputbox "SSH public key file to inject (blank = skip)" 8 58 "$HOME/.ssh/id_rsa.pub" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
pct status "$CTID" >/dev/null 2>&1 && die "CT $CTID already exists."
|
||||||
|
|
||||||
|
# ---------- template ----------
|
||||||
|
mapfile -t TEMPLATES < <(pveam available -section system | awk '{print $2}' | grep -i opensuse || true)
|
||||||
|
if [[ ${#TEMPLATES[@]} -eq 0 ]]; then
|
||||||
|
msg_info "Refreshing template index..."
|
||||||
|
pveam update
|
||||||
|
mapfile -t TEMPLATES < <(pveam available -section system | awk '{print $2}' | grep -i opensuse || true)
|
||||||
|
fi
|
||||||
|
[[ ${#TEMPLATES[@]} -gt 0 ]] || die "No openSUSE templates found in the Proxmox appliance list."
|
||||||
|
|
||||||
|
if [[ "$MODE" == "default" ]]; then
|
||||||
|
TEMPLATE="${TEMPLATES[-1]}" # newest listed
|
||||||
|
else
|
||||||
|
RADIO_ARGS=(); first=1
|
||||||
|
for t in "${TEMPLATES[@]}"; do
|
||||||
|
if [[ $first -eq 1 ]]; then RADIO_ARGS+=("$t" "" ON); first=0; else RADIO_ARGS+=("$t" "" OFF); fi
|
||||||
|
done
|
||||||
|
TEMPLATE=$(whiptail --radiolist "Choose openSUSE template" 20 72 10 "${RADIO_ARGS[@]}" --title "$APP LXC" 3>&1 1>&2 2>&3) || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! pveam list local | grep -q "$TEMPLATE"; then
|
||||||
|
msg_info "Downloading template $TEMPLATE..."
|
||||||
|
pveam download local "$TEMPLATE"
|
||||||
|
msg_ok "Template downloaded."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------- confirm ----------
|
||||||
|
SUMMARY="CTID: $CTID
|
||||||
|
Hostname: $CT_HOSTNAME
|
||||||
|
Template: $TEMPLATE
|
||||||
|
Storage: $CT_STORAGE (${DISK_GB}G)
|
||||||
|
CPU/RAM: ${CORES} cores / ${MEMORY}MB (+${SWAP}MB swap)
|
||||||
|
Bridge: $BRIDGE${VLAN:+ (vlan $VLAN)}
|
||||||
|
Network: ${NET_MODE}${STATIC_IP_CIDR:+ $STATIC_IP_CIDR via $STATIC_GW}
|
||||||
|
Priv: $([[ $UNPRIVILEGED -eq 1 ]] && echo unprivileged || echo privileged)
|
||||||
|
Grafana: port $GRAFANA_PORT"
|
||||||
|
|
||||||
|
whiptail --title "Confirm" --yesno "$SUMMARY
|
||||||
|
|
||||||
|
Proceed?" 20 65 || exit 1
|
||||||
|
|
||||||
|
# ---------- build net string ----------
|
||||||
|
if [[ "$NET_MODE" == "static" ]]; then
|
||||||
|
NET_STR="name=eth0,bridge=${BRIDGE},ip=${STATIC_IP_CIDR},gw=${STATIC_GW}"
|
||||||
|
else
|
||||||
|
NET_STR="name=eth0,bridge=${BRIDGE},ip=dhcp"
|
||||||
|
fi
|
||||||
|
[[ -n "$VLAN" ]] && NET_STR="${NET_STR},tag=${VLAN}"
|
||||||
|
|
||||||
|
# ---------- create + start ----------
|
||||||
|
CREATE_ARGS=(
|
||||||
|
"$CTID" "local:vztmpl/${TEMPLATE}"
|
||||||
|
--hostname "$CT_HOSTNAME"
|
||||||
|
--cores "$CORES"
|
||||||
|
--memory "$MEMORY"
|
||||||
|
--swap "$SWAP"
|
||||||
|
--rootfs "${CT_STORAGE}:${DISK_GB}"
|
||||||
|
--net0 "$NET_STR"
|
||||||
|
--unprivileged "$UNPRIVILEGED"
|
||||||
|
--onboot 1
|
||||||
|
--features "nesting=1"
|
||||||
|
--password "$ROOT_PASSWORD"
|
||||||
|
)
|
||||||
|
[[ -n "${SSH_PUBKEY_FILE:-}" && -f "${SSH_PUBKEY_FILE:-}" ]] && CREATE_ARGS+=(--ssh-public-keys "$SSH_PUBKEY_FILE")
|
||||||
|
|
||||||
|
msg_info "Creating CT $CTID ($CT_HOSTNAME)..."
|
||||||
|
pct create "${CREATE_ARGS[@]}" >/dev/null
|
||||||
|
msg_ok "Container created."
|
||||||
|
|
||||||
|
msg_info "Starting CT $CTID..."
|
||||||
|
pct start "$CTID"
|
||||||
|
|
||||||
|
msg_info "Waiting for network..."
|
||||||
|
for i in {1..30}; do
|
||||||
|
pct exec "$CTID" -- sh -c "true" >/dev/null 2>&1 && pct exec "$CTID" -- ping -c1 -W2 1.1.1.1 >/dev/null 2>&1 && break
|
||||||
|
sleep 2
|
||||||
|
[[ $i -eq 30 ]] && die "CT never came up with network access."
|
||||||
|
done
|
||||||
|
msg_ok "Network is up."
|
||||||
|
|
||||||
|
# ---------- install grafana inside the CT ----------
|
||||||
|
run() { pct exec "$CTID" -- bash -c "$1"; }
|
||||||
|
|
||||||
|
msg_info "Refreshing zypper repos..."
|
||||||
|
run "zypper --non-interactive refresh" >/dev/null
|
||||||
|
msg_ok "Repos refreshed."
|
||||||
|
|
||||||
|
msg_info "Installing prerequisites (curl, gpg2, firewalld)..."
|
||||||
|
run "zypper --non-interactive install curl gpg2 firewalld" >/dev/null
|
||||||
|
msg_ok "Prerequisites installed."
|
||||||
|
|
||||||
|
msg_info "Adding Grafana repo..."
|
||||||
|
run "zypper -n ar --refresh https://rpm.grafana.com grafana || true"
|
||||||
|
run "zypper --gpg-auto-import-keys --non-interactive refresh" >/dev/null
|
||||||
|
msg_ok "Grafana repo added."
|
||||||
|
|
||||||
|
msg_info "Installing Grafana..."
|
||||||
|
run "zypper --non-interactive install grafana" >/dev/null
|
||||||
|
msg_ok "Grafana installed."
|
||||||
|
|
||||||
|
msg_info "Configuring Grafana (port ${GRAFANA_PORT}, admin password)..."
|
||||||
|
run "sed -i '/^\[server\]/,/^\[/{s/^;\{0,1\}[[:space:]]*http_port[[:space:]]*=.*/http_port = ${GRAFANA_PORT}/}' /etc/grafana/grafana.ini"
|
||||||
|
run "sed -i '/^\[security\]/,/^\[/{s/^;\{0,1\}[[:space:]]*admin_password[[:space:]]*=.*/admin_password = ${GRAFANA_ADMIN_PASSWORD}/}' /etc/grafana/grafana.ini"
|
||||||
|
msg_ok "Grafana configured."
|
||||||
|
|
||||||
|
msg_info "Enabling firewalld and opening ports 22/${GRAFANA_PORT}..."
|
||||||
|
run "systemctl enable --now firewalld" >/dev/null
|
||||||
|
run "firewall-cmd --permanent --add-port=22/tcp" >/dev/null
|
||||||
|
run "firewall-cmd --permanent --add-port=${GRAFANA_PORT}/tcp" >/dev/null
|
||||||
|
run "firewall-cmd --reload" >/dev/null
|
||||||
|
msg_ok "Firewall configured."
|
||||||
|
|
||||||
|
msg_info "Starting Grafana..."
|
||||||
|
run "systemctl enable --now grafana-server" >/dev/null
|
||||||
|
|
||||||
|
msg_info "Waiting for Grafana to answer..."
|
||||||
|
for i in {1..15}; do
|
||||||
|
run "curl -sf http://localhost:${GRAFANA_PORT}/api/health >/dev/null" && break
|
||||||
|
sleep 3
|
||||||
|
[[ $i -eq 15 ]] && die "Grafana did not come up in time — check: pct exec $CTID -- systemctl status grafana-server"
|
||||||
|
done
|
||||||
|
msg_ok "Grafana is up."
|
||||||
|
|
||||||
|
# ---------- summary ----------
|
||||||
|
CT_IP=$(pct exec "$CTID" -- hostname -I 2>/dev/null | awk '{print $1}')
|
||||||
|
|
||||||
|
whiptail --title "$APP LXC Installer — Done" --msgbox \
|
||||||
|
"Grafana is running.
|
||||||
|
|
||||||
|
CT ID: $CTID
|
||||||
|
URL: http://${CT_IP}:${GRAFANA_PORT}
|
||||||
|
User: admin
|
||||||
|
Password: ${GRAFANA_ADMIN_PASSWORD}
|
||||||
|
Root pass: ${ROOT_PASSWORD}
|
||||||
|
|
||||||
|
(also printed below in the terminal)" 16 65
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
========================================
|
||||||
|
$APP LXC ready
|
||||||
|
========================================
|
||||||
|
CT ID: $CTID
|
||||||
|
URL: http://${CT_IP}:${GRAFANA_PORT}
|
||||||
|
Admin user: admin
|
||||||
|
Admin pass: ${GRAFANA_ADMIN_PASSWORD}
|
||||||
|
Root pass: ${ROOT_PASSWORD}
|
||||||
|
========================================
|
||||||
|
EOF
|
||||||
Reference in new issue
Block a user