lxc.sh
· 9.2 KiB · Bash
Bruto
#!/bin/bash
#
# Proxmox LXC Container Creator v2.1
# Автоматично създаване и конфигуриране на LXC контейнери
# Автор: Федя Серафиев / urocibg.eu
set -euo pipefail
# 🌈 Цветове и Функции
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'; BOLD='\033[1m'
print_header() {
clear
echo -e "${CYAN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n $1\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
print_success() {
echo -e "${GREEN}✅ $1${NC}"
}
print_error() {
echo -e "${RED}❌ ГРЕШКА: $1${NC}"
}
# ----------------------------------------------------------------------------------
# 🌐 Избор на ОС Шаблон (ДИНАМИЧЕН ИЗБОР - С HTTP ЛИНК)
# ----------------------------------------------------------------------------------
BASE_URL="http://download.proxmox.com/images/system/"
print_header "PROXMOX LXC СЪЗДАТЕЛ – ДИНАМИЧЕН ИЗБОР НА ШАБЛОН"
print_success "Свалям списък с най-новите LXC шаблони от $BASE_URL..."
# Сваляне, филтриране и сортиране на последните 10 шаблона, като изключваме almalinux
mapfile -t ALL_TEMPLATES < <(wget -q -O - "$BASE_URL" | \
grep -o 'href="[^"]*\(tar\.zst\|tar\.xz\)"' | \
grep -v 'almalinux' | \
awk -F'"' '{print $2}' | \
sort -r | \
head -n 10)
if [ ${#ALL_TEMPLATES[@]} -eq 0 ]; then
print_error "Неуспешно изтегляне на списъка с шаблони. Използвам резервен (статичен) списък."
# Резервен (статичен) списък
ALL_TEMPLATES=(
"debian-12-standard_12.7-1_amd64.tar.xz"
"ubuntu-24.04-standard_24.04-2_amd64.tar.zst"
"debian-11-standard_11.9-1_amd64.tar.zst"
"ubuntu-22.04-standard_22.04-1_amd64.tar.zst"
)
fi
echo -e "\n${BOLD}Избери ОС (най-новите налични шаблони):${NC}"
# Показваме списъка
i=1
for tmpl in "${ALL_TEMPLATES[@]}"; do
# Форматиране на името за по-кратък и ясен изглед
DISPLAY_NAME=$(echo "$tmpl" | sed -E 's/(-standard|_default|(_[0-9]+\.?[0-9]*)+_amd64\.tar\.(zst|xz))//g' | tr '-' ' ' | awk '{$1=toupper(substr($1,1,1)) substr($1,2)}1')
RECOMMENDATION=""
if [[ "$tmpl" =~ ^debian-12 ]]; then
RECOMMENDATION="← Препоръчителен"
fi
echo -e " $i) $DISPLAY_NAME $RECOMMENDATION"
((i++))
done
# Избор на шаблон
while :; do
read -p "Въведи 1–$((i-1)) [1]: " os_choice
os_choice=${os_choice:-1}
if [[ "$os_choice" =~ ^[0-9]+$ ]] && (( os_choice >= 1 && os_choice < i )); then
TEMPLATE="${ALL_TEMPLATES[os_choice-1]}"
# URL за сваляне на избрания шаблон
URL="$BASE_URL$TEMPLATE"
break
else
print_error "Невалиден избор."
fi
done
# ----------------------------------------------------------------------------------
# 💡 Настройки на Контейнера
# ----------------------------------------------------------------------------------
while :; do
read -p "CT ID (напр. 100): " CTID
if [[ $CTID =~ ^[0-9]+$ ]] && (( CTID >= 100 )); then
if ! pct status "$CTID" &>/dev/null; then
break
else
print_error "Контейнер с ID $CTID вече съществува или е зает."
fi
else
print_error "Невалидно CT ID. Трябва да е число >= 100."
fi
done
read -p "Hostname [lxc-$CTID]: " HOSTNAME; HOSTNAME=${HOSTNAME:-lxc-$CTID}
read -p "Storage [local-lvm]: " STORAGE; STORAGE=${STORAGE:-local-lvm}
read -p "Disk GB [20]: " DISK; DISK=${DISK:-20}
read -p "RAM MB [4096]: " RAM; RAM=${RAM:-4096}
read -p "CPU ядра [2]: " CORES; CORES=${CORES:-2}
read -p "Bridge [vmbr0]: " BRIDGE; BRIDGE=${BRIDGE:-vmbr0}
# 🌐 Мрежови Настройки
IP=""; GW=""; DNS=""
read -p "Статично IP? (y/N): " yn
if [[ $yn =~ ^[Yy]$ ]]; then
read -p "IP/MASK (напр. 10.0.0.178/24): " IP
read -p "Gateway (Enter = без): " GW
DNS="1.1.1.1 8.8.8.8"
fi
# 🔑 SSH Ключ / Парола
SSH_KEY_ARG=""
PASSWORD_ARG=""
KEY_TEMP_FILE=""
echo -e "\n${BOLD}Избери метод за достъп (SSH ключ или парола):${NC}"
echo " 1) Добавяне на SSH публичен ключ"
echo " 2) Използване на root парола"
read -p "Въведи 1 или 2 [1]: " auth_choice
if [[ "${auth_choice:-1}" == "1" ]]; then
read -p "Път до SSH публичен ключ (Enter за директно поставяне): " keyfile
SSH_KEY_CONTENT=""
if [[ -f "$keyfile" ]]; then
SSH_KEY_CONTENT=$(cat "$keyfile")
print_success "Прочетен ключ от файл: $keyfile"
elif [[ -z "$keyfile" ]]; then
echo -e "${YELLOW}Моля, поставете целия си публичен SSH ключ и натиснете Enter:${NC}"
read SSH_KEY_CONTENT
if [[ -z "$SSH_KEY_CONTENT" ]]; then
print_error "Не е въведен SSH ключ."
auth_choice=2
fi
else
print_error "Файлът с ключ не е намерен ($keyfile)."
auth_choice=2
fi
if [[ -n "${SSH_KEY_CONTENT:-}" ]]; then
KEY_TEMP_FILE=$(mktemp)
echo "$SSH_KEY_CONTENT" > "$KEY_TEMP_FILE"
SSH_KEY_ARG="--ssh-public-keys $KEY_TEMP_FILE"
fi
fi
if [[ "${auth_choice:-1}" == "2" ]]; then
while :; do
read -s -p "Root парола: " PASSWORD; echo
read -s -p "Повтори парола: " p2; echo
if [[ "$PASSWORD" == "$p2" ]]; then
PASSWORD_ARG="--password \"$PASSWORD\""
break
else
print_error "Паролите не съвпадат. Опитай пак."
fi
done
fi
# 🔒 Привилегирован/Непривилегирован
read -p "Непривилегирован контейнер (препоръчва се)? (Y/n): " u
[[ $u =~ ^[Nn]$ ]] && UNPRIVILEGED=0 || UNPRIVILEGED=1
# 📝 Резюме
print_header "ПРЕГЛЕД НА НАСТРОЙКИТЕ"
echo -e "ОС: $TEMPLATE"
echo -e "CTID: $CTID | Hostname: $HOSTNAME"
echo -e "Спецификации: $DISK GB / $RAM MB RAM / $CORES ядра (Swap: 0)"
echo -e "Мрежа: $BRIDGE | IP: ${IP:-DHCP}"
echo -e "Достъп: $([[ -n "$SSH_KEY_ARG" ]] && echo "SSH Key" || echo "Root Парола")"
echo -e "Тип: $([[ "$UNPRIVILEGED" == "1" ]] && echo "Непривилегирован" || echo "Привилегирован")"
read -p "ГОТОВО? (y/N): " ok
[[ $ok =~ ^[Yy]$ ]] || { [[ -n "${KEY_TEMP_FILE:-}" ]] && rm "$KEY_TEMP_FILE"; exit 0; }
# ⬇️ Сваляне на Шаблона
tmpl="/var/lib/vz/template/cache/$TEMPLATE"
if [[ ! -f "$tmpl" ]]; then
print_success "Свалям $TEMPLATE ..."
wget -q --show-progress -O "$tmpl" "$URL"
else
print_success "Шаблонът $TEMPLATE вече съществува, пропускам свалянето."
fi
# ⚙️ Подготовка на Мрежата
net="name=eth0,bridge=$BRIDGE,firewall=1"
[[ -n "${IP:-}" ]] && net+=",ip=$IP"
[[ -n "${GW:-}" ]] && net+=",gw=$GW"
# 🏗️ Създаване на Контейнера
print_success "Създавам контейнер $CTID ..."
# Аргументи: --swap 0 и настройки за сигурност
COMMAND="pct create \"$CTID\" \"$tmpl\" \
--hostname \"$HOSTNAME\" --storage \"$STORAGE\" --rootfs \"$STORAGE:$DISK\" \
--memory \"$RAM\" --cores \"$CORES\" --net0 \"$net\" --swap 0 \
--features nesting=1,keyctl=1 --onboot 1 --unprivileged \"$UNPRIVILEGED\" \
$SSH_KEY_ARG \
$PASSWORD_ARG"
if ! eval "$COMMAND"; then
print_error "Неуспешно създаване на контейнера. Проверете настройките и логовете на Proxmox."
[[ -n "${KEY_TEMP_FILE:-}" ]] && rm "$KEY_TEMP_FILE"
exit 1
fi
# 📝 Добавяне на DNS
[[ -n "${IP:-}" ]] && pct set "$CTID" --nameserver "$DNS"
# 🚀 Стартиране и Първоначална Настройка
print_success "Стартирам контейнер $CTID..."
pct start "$CTID"
print_success "Изчаквам и инсталирам основни пакети (около 30-50 секунди)..."
sleep 8
pct exec "$CTID" -- bash -c "apt update && apt upgrade -y && apt install -y sudo curl wget git nano htop net-tools qemu-guest-agent" >/dev/null 2>&1
# 🥳 Край
print_header "ГОТОВО!"
echo -e "${GREEN}Контейнер $CTID ($HOSTNAME) е готов и работи!${NC}"
echo -e "Влез с: ${CYAN}pct enter $CTID${NC}"
[[ -n "$IP" ]] && echo -e "или с: ${CYAN}ssh root@$(echo $IP | cut -d/ -f1)${NC}"
# 🧹 Почистване на временен файл, ако е създаден
[[ -n "${KEY_TEMP_FILE:-}" ]] && rm "$KEY_TEMP_FILE"
| 1 | #!/bin/bash |
| 2 | # |
| 3 | # Proxmox LXC Container Creator v2.1 |
| 4 | # Автоматично създаване и конфигуриране на LXC контейнери |
| 5 | # Автор: Федя Серафиев / urocibg.eu |
| 6 | |
| 7 | set -euo pipefail |
| 8 | |
| 9 | # 🌈 Цветове и Функции |
| 10 | RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'; BOLD='\033[1m' |
| 11 | |
| 12 | print_header() { |
| 13 | clear |
| 14 | echo -e "${CYAN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n $1\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" |
| 15 | } |
| 16 | |
| 17 | print_success() { |
| 18 | echo -e "${GREEN}✅ $1${NC}" |
| 19 | } |
| 20 | |
| 21 | print_error() { |
| 22 | echo -e "${RED}❌ ГРЕШКА: $1${NC}" |
| 23 | } |
| 24 | |
| 25 | # ---------------------------------------------------------------------------------- |
| 26 | # 🌐 Избор на ОС Шаблон (ДИНАМИЧЕН ИЗБОР - С HTTP ЛИНК) |
| 27 | # ---------------------------------------------------------------------------------- |
| 28 | BASE_URL="http://download.proxmox.com/images/system/" |
| 29 | print_header "PROXMOX LXC СЪЗДАТЕЛ – ДИНАМИЧЕН ИЗБОР НА ШАБЛОН" |
| 30 | |
| 31 | print_success "Свалям списък с най-новите LXC шаблони от $BASE_URL..." |
| 32 | |
| 33 | # Сваляне, филтриране и сортиране на последните 10 шаблона, като изключваме almalinux |
| 34 | mapfile -t ALL_TEMPLATES < <(wget -q -O - "$BASE_URL" | \ |
| 35 | grep -o 'href="[^"]*\(tar\.zst\|tar\.xz\)"' | \ |
| 36 | grep -v 'almalinux' | \ |
| 37 | awk -F'"' '{print $2}' | \ |
| 38 | sort -r | \ |
| 39 | head -n 10) |
| 40 | |
| 41 | if [ ${#ALL_TEMPLATES[@]} -eq 0 ]; then |
| 42 | print_error "Неуспешно изтегляне на списъка с шаблони. Използвам резервен (статичен) списък." |
| 43 | # Резервен (статичен) списък |
| 44 | ALL_TEMPLATES=( |
| 45 | "debian-12-standard_12.7-1_amd64.tar.xz" |
| 46 | "ubuntu-24.04-standard_24.04-2_amd64.tar.zst" |
| 47 | "debian-11-standard_11.9-1_amd64.tar.zst" |
| 48 | "ubuntu-22.04-standard_22.04-1_amd64.tar.zst" |
| 49 | ) |
| 50 | fi |
| 51 | |
| 52 | echo -e "\n${BOLD}Избери ОС (най-новите налични шаблони):${NC}" |
| 53 | |
| 54 | # Показваме списъка |
| 55 | i=1 |
| 56 | for tmpl in "${ALL_TEMPLATES[@]}"; do |
| 57 | # Форматиране на името за по-кратък и ясен изглед |
| 58 | DISPLAY_NAME=$(echo "$tmpl" | sed -E 's/(-standard|_default|(_[0-9]+\.?[0-9]*)+_amd64\.tar\.(zst|xz))//g' | tr '-' ' ' | awk '{$1=toupper(substr($1,1,1)) substr($1,2)}1') |
| 59 | |
| 60 | RECOMMENDATION="" |
| 61 | if [[ "$tmpl" =~ ^debian-12 ]]; then |
| 62 | RECOMMENDATION="← Препоръчителен" |
| 63 | fi |
| 64 | |
| 65 | echo -e " $i) $DISPLAY_NAME $RECOMMENDATION" |
| 66 | ((i++)) |
| 67 | done |
| 68 | |
| 69 | # Избор на шаблон |
| 70 | while :; do |
| 71 | read -p "Въведи 1–$((i-1)) [1]: " os_choice |
| 72 | os_choice=${os_choice:-1} |
| 73 | if [[ "$os_choice" =~ ^[0-9]+$ ]] && (( os_choice >= 1 && os_choice < i )); then |
| 74 | TEMPLATE="${ALL_TEMPLATES[os_choice-1]}" |
| 75 | # URL за сваляне на избрания шаблон |
| 76 | URL="$BASE_URL$TEMPLATE" |
| 77 | break |
| 78 | else |
| 79 | print_error "Невалиден избор." |
| 80 | fi |
| 81 | done |
| 82 | |
| 83 | # ---------------------------------------------------------------------------------- |
| 84 | # 💡 Настройки на Контейнера |
| 85 | # ---------------------------------------------------------------------------------- |
| 86 | while :; do |
| 87 | read -p "CT ID (напр. 100): " CTID |
| 88 | if [[ $CTID =~ ^[0-9]+$ ]] && (( CTID >= 100 )); then |
| 89 | if ! pct status "$CTID" &>/dev/null; then |
| 90 | break |
| 91 | else |
| 92 | print_error "Контейнер с ID $CTID вече съществува или е зает." |
| 93 | fi |
| 94 | else |
| 95 | print_error "Невалидно CT ID. Трябва да е число >= 100." |
| 96 | fi |
| 97 | done |
| 98 | |
| 99 | read -p "Hostname [lxc-$CTID]: " HOSTNAME; HOSTNAME=${HOSTNAME:-lxc-$CTID} |
| 100 | read -p "Storage [local-lvm]: " STORAGE; STORAGE=${STORAGE:-local-lvm} |
| 101 | read -p "Disk GB [20]: " DISK; DISK=${DISK:-20} |
| 102 | read -p "RAM MB [4096]: " RAM; RAM=${RAM:-4096} |
| 103 | read -p "CPU ядра [2]: " CORES; CORES=${CORES:-2} |
| 104 | read -p "Bridge [vmbr0]: " BRIDGE; BRIDGE=${BRIDGE:-vmbr0} |
| 105 | |
| 106 | # 🌐 Мрежови Настройки |
| 107 | IP=""; GW=""; DNS="" |
| 108 | read -p "Статично IP? (y/N): " yn |
| 109 | if [[ $yn =~ ^[Yy]$ ]]; then |
| 110 | read -p "IP/MASK (напр. 10.0.0.178/24): " IP |
| 111 | read -p "Gateway (Enter = без): " GW |
| 112 | DNS="1.1.1.1 8.8.8.8" |
| 113 | fi |
| 114 | |
| 115 | # 🔑 SSH Ключ / Парола |
| 116 | SSH_KEY_ARG="" |
| 117 | PASSWORD_ARG="" |
| 118 | KEY_TEMP_FILE="" |
| 119 | |
| 120 | echo -e "\n${BOLD}Избери метод за достъп (SSH ключ или парола):${NC}" |
| 121 | echo " 1) Добавяне на SSH публичен ключ" |
| 122 | echo " 2) Използване на root парола" |
| 123 | read -p "Въведи 1 или 2 [1]: " auth_choice |
| 124 | |
| 125 | if [[ "${auth_choice:-1}" == "1" ]]; then |
| 126 | read -p "Път до SSH публичен ключ (Enter за директно поставяне): " keyfile |
| 127 | |
| 128 | SSH_KEY_CONTENT="" |
| 129 | if [[ -f "$keyfile" ]]; then |
| 130 | SSH_KEY_CONTENT=$(cat "$keyfile") |
| 131 | print_success "Прочетен ключ от файл: $keyfile" |
| 132 | elif [[ -z "$keyfile" ]]; then |
| 133 | echo -e "${YELLOW}Моля, поставете целия си публичен SSH ключ и натиснете Enter:${NC}" |
| 134 | read SSH_KEY_CONTENT |
| 135 | if [[ -z "$SSH_KEY_CONTENT" ]]; then |
| 136 | print_error "Не е въведен SSH ключ." |
| 137 | auth_choice=2 |
| 138 | fi |
| 139 | else |
| 140 | print_error "Файлът с ключ не е намерен ($keyfile)." |
| 141 | auth_choice=2 |
| 142 | fi |
| 143 | |
| 144 | if [[ -n "${SSH_KEY_CONTENT:-}" ]]; then |
| 145 | KEY_TEMP_FILE=$(mktemp) |
| 146 | echo "$SSH_KEY_CONTENT" > "$KEY_TEMP_FILE" |
| 147 | SSH_KEY_ARG="--ssh-public-keys $KEY_TEMP_FILE" |
| 148 | fi |
| 149 | fi |
| 150 | |
| 151 | if [[ "${auth_choice:-1}" == "2" ]]; then |
| 152 | while :; do |
| 153 | read -s -p "Root парола: " PASSWORD; echo |
| 154 | read -s -p "Повтори парола: " p2; echo |
| 155 | if [[ "$PASSWORD" == "$p2" ]]; then |
| 156 | PASSWORD_ARG="--password \"$PASSWORD\"" |
| 157 | break |
| 158 | else |
| 159 | print_error "Паролите не съвпадат. Опитай пак." |
| 160 | fi |
| 161 | done |
| 162 | fi |
| 163 | |
| 164 | # 🔒 Привилегирован/Непривилегирован |
| 165 | read -p "Непривилегирован контейнер (препоръчва се)? (Y/n): " u |
| 166 | [[ $u =~ ^[Nn]$ ]] && UNPRIVILEGED=0 || UNPRIVILEGED=1 |
| 167 | |
| 168 | # 📝 Резюме |
| 169 | print_header "ПРЕГЛЕД НА НАСТРОЙКИТЕ" |
| 170 | echo -e "ОС: $TEMPLATE" |
| 171 | echo -e "CTID: $CTID | Hostname: $HOSTNAME" |
| 172 | echo -e "Спецификации: $DISK GB / $RAM MB RAM / $CORES ядра (Swap: 0)" |
| 173 | echo -e "Мрежа: $BRIDGE | IP: ${IP:-DHCP}" |
| 174 | echo -e "Достъп: $([[ -n "$SSH_KEY_ARG" ]] && echo "SSH Key" || echo "Root Парола")" |
| 175 | echo -e "Тип: $([[ "$UNPRIVILEGED" == "1" ]] && echo "Непривилегирован" || echo "Привилегирован")" |
| 176 | |
| 177 | read -p "ГОТОВО? (y/N): " ok |
| 178 | [[ $ok =~ ^[Yy]$ ]] || { [[ -n "${KEY_TEMP_FILE:-}" ]] && rm "$KEY_TEMP_FILE"; exit 0; } |
| 179 | |
| 180 | # ⬇️ Сваляне на Шаблона |
| 181 | tmpl="/var/lib/vz/template/cache/$TEMPLATE" |
| 182 | if [[ ! -f "$tmpl" ]]; then |
| 183 | print_success "Свалям $TEMPLATE ..." |
| 184 | wget -q --show-progress -O "$tmpl" "$URL" |
| 185 | else |
| 186 | print_success "Шаблонът $TEMPLATE вече съществува, пропускам свалянето." |
| 187 | fi |
| 188 | |
| 189 | # ⚙️ Подготовка на Мрежата |
| 190 | net="name=eth0,bridge=$BRIDGE,firewall=1" |
| 191 | [[ -n "${IP:-}" ]] && net+=",ip=$IP" |
| 192 | [[ -n "${GW:-}" ]] && net+=",gw=$GW" |
| 193 | |
| 194 | # 🏗️ Създаване на Контейнера |
| 195 | print_success "Създавам контейнер $CTID ..." |
| 196 | |
| 197 | # Аргументи: --swap 0 и настройки за сигурност |
| 198 | COMMAND="pct create \"$CTID\" \"$tmpl\" \ |
| 199 | --hostname \"$HOSTNAME\" --storage \"$STORAGE\" --rootfs \"$STORAGE:$DISK\" \ |
| 200 | --memory \"$RAM\" --cores \"$CORES\" --net0 \"$net\" --swap 0 \ |
| 201 | --features nesting=1,keyctl=1 --onboot 1 --unprivileged \"$UNPRIVILEGED\" \ |
| 202 | $SSH_KEY_ARG \ |
| 203 | $PASSWORD_ARG" |
| 204 | |
| 205 | if ! eval "$COMMAND"; then |
| 206 | print_error "Неуспешно създаване на контейнера. Проверете настройките и логовете на Proxmox." |
| 207 | [[ -n "${KEY_TEMP_FILE:-}" ]] && rm "$KEY_TEMP_FILE" |
| 208 | exit 1 |
| 209 | fi |
| 210 | |
| 211 | # 📝 Добавяне на DNS |
| 212 | [[ -n "${IP:-}" ]] && pct set "$CTID" --nameserver "$DNS" |
| 213 | |
| 214 | # 🚀 Стартиране и Първоначална Настройка |
| 215 | print_success "Стартирам контейнер $CTID..." |
| 216 | pct start "$CTID" |
| 217 | |
| 218 | print_success "Изчаквам и инсталирам основни пакети (около 30-50 секунди)..." |
| 219 | sleep 8 |
| 220 | pct exec "$CTID" -- bash -c "apt update && apt upgrade -y && apt install -y sudo curl wget git nano htop net-tools qemu-guest-agent" >/dev/null 2>&1 |
| 221 | |
| 222 | # 🥳 Край |
| 223 | print_header "ГОТОВО!" |
| 224 | echo -e "${GREEN}Контейнер $CTID ($HOSTNAME) е готов и работи!${NC}" |
| 225 | echo -e "Влез с: ${CYAN}pct enter $CTID${NC}" |
| 226 | [[ -n "$IP" ]] && echo -e "или с: ${CYAN}ssh root@$(echo $IP | cut -d/ -f1)${NC}" |
| 227 | |
| 228 | # 🧹 Почистване на временен файл, ако е създаден |
| 229 | [[ -n "${KEY_TEMP_FILE:-}" ]] && rm "$KEY_TEMP_FILE" |