Skip to content
This repository was archived by the owner on Apr 14, 2026. It is now read-only.
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
309 changes: 309 additions & 0 deletions proxmox-host-setup/heyform/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
#!/usr/bin/env bash
# ⚠️ 이 슀크립트의 μ‹€ν–‰μœΌλ‘œ λ°œμƒν•˜λŠ” λͺ¨λ“  결과에 λŒ€ν•œ κ·€μ±…μ‚¬μœ λŠ” μ‹€ν–‰μž λ³ΈμΈμ—κ²Œ μžˆμŠ΅λ‹ˆλ‹€.
#
# install.sh β€” HeyForm LXC μ»¨ν…Œμ΄λ„ˆ μ„€μΉ˜ μžλ™ν™”
#
# Proxmox VE ν˜ΈμŠ€νŠΈμ—μ„œ μ‹€ν–‰:
# CTID=50180 CT_IP=10.0.0.180/24 CT_GW=10.0.0.1 ./install.sh
#
# ν™˜κ²½λ³€μˆ˜:
# CTID - μ»¨ν…Œμ΄λ„ˆ ID (ν•„μˆ˜)
# CT_HOSTNAME - 호슀트λͺ… (default: heyform)
# CT_IP - IP/CIDR (ν•„μˆ˜, e.g. 10.0.0.180/24)
# CT_GW - κ²Œμ΄νŠΈμ›¨μ΄ (ν•„μˆ˜, e.g. 10.0.0.1)
# CT_MEMORY - λ©”λͺ¨λ¦¬ MB (default: 2048)
# CT_CORES - CPU μ½”μ–΄ (default: 2)
# CT_DISK - λ””μŠ€ν¬ GB (default: 16)
# CT_STORAGE - μŠ€ν† λ¦¬μ§€ (default: local-lvm)
# CT_BRIDGE - λ„€νŠΈμ›Œν¬ λΈŒλ¦Ώμ§€ (default: vmbr0)
# CT_NAMESERVER - DNS (default: 8.8.8.8)
# CT_TEMPLATE - OS ν…œν”Œλ¦Ώ (default: debian-12-standard)
# HEYFORM_PORT - HeyForm 포트 (default: 9513)
# APP_HOMEPAGE_URL - HeyForm 접속 URL (default: http://<CT_IP>:9513)

set -euo pipefail

# ─── 색상 ─────────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

log() { echo -e "${GREEN}[heyform]${NC} $*"; }
info() { echo -e "${BLUE}[heyform]${NC} $*"; }
warn() { echo -e "${YELLOW}[heyform]${NC} $*"; }
err() { echo -e "${RED}[heyform]${NC} $*" >&2; }

# ─── ν™˜κ²½λ³€μˆ˜ κΈ°λ³Έκ°’ ──────────────────────────────────────────────────
CT_HOSTNAME="${CT_HOSTNAME:-heyform}"
CT_MEMORY="${CT_MEMORY:-2048}"
CT_CORES="${CT_CORES:-2}"
CT_DISK="${CT_DISK:-16}"
CT_STORAGE="${CT_STORAGE:-local-lvm}"
CT_BRIDGE="${CT_BRIDGE:-vmbr0}"
CT_NAMESERVER="${CT_NAMESERVER:-8.8.8.8}"
CT_TEMPLATE="${CT_TEMPLATE:-debian-12-standard}"
HEYFORM_PORT="${HEYFORM_PORT:-9513}"

# ─── μž…λ ₯ 검증 ────────────────────────────────────────────────────────
validate_inputs() {
local errors=0

if [ -z "${CTID:-}" ]; then
err "CTIDκ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. (예: CTID=50180)"
errors=$((errors + 1))
fi

if [ -z "${CT_IP:-}" ]; then
err "CT_IPκ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. (예: CT_IP=10.0.0.180/24)"
errors=$((errors + 1))
fi

if [ -z "${CT_GW:-}" ]; then
err "CT_GWκ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. (예: CT_GW=10.0.0.1)"
errors=$((errors + 1))
fi

if [ $errors -gt 0 ]; then
echo ""
err "μ‚¬μš©λ²•: CTID=50180 CT_IP=10.0.0.180/24 CT_GW=10.0.0.1 $0"
exit 1
fi

# CTIDκ°€ 이미 μ‚¬μš© 쀑인지 확인
if pct status "$CTID" &>/dev/null; then
err "CTID $CTID λŠ” 이미 μ‘΄μž¬ν•©λ‹ˆλ‹€."
exit 1
fi

# IPμ—μ„œ CIDR μ œκ±°ν•œ 순수 IP μΆ”μΆœ
CT_IP_BARE="${CT_IP%%/*}"
APP_HOMEPAGE_URL="${APP_HOMEPAGE_URL:-http://${CT_IP_BARE}:${HEYFORM_PORT}}"
}

# ─── [1/6] ν…œν”Œλ¦Ώ λ‹€μš΄λ‘œλ“œ ────────────────────────────────────────────
download_template() {
log "[1/6] OS ν…œν”Œλ¦Ώ 확인 쀑..."

local template_path
template_path=$(pveam list local 2>/dev/null | grep "$CT_TEMPLATE" | awk '{print $1}' | head -1)

if [ -z "$template_path" ]; then
info "ν…œν”Œλ¦Ώ λ‹€μš΄λ‘œλ“œ 쀑: ${CT_TEMPLATE}..."
pveam update >/dev/null 2>&1
local available
available=$(pveam available --section system 2>/dev/null | grep "$CT_TEMPLATE" | awk '{print $2}' | sort -V | tail -1)
if [ -z "$available" ]; then
err "ν…œν”Œλ¦Ώ '$CT_TEMPLATE'을 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."
exit 1
fi
pveam download local "$available"
template_path="local:vztmpl/${available}"
fi

TEMPLATE_PATH="$template_path"
log " ν…œν”Œλ¦Ώ: ${TEMPLATE_PATH}"
}

# ─── [2/6] μ»¨ν…Œμ΄λ„ˆ 생성 ──────────────────────────────────────────────
create_container() {
log "[2/6] LXC μ»¨ν…Œμ΄λ„ˆ 생성 (CTID: ${CTID})..."

pct create "$CTID" "$TEMPLATE_PATH" \
--hostname "$CT_HOSTNAME" \
--memory "$CT_MEMORY" \
--cores "$CT_CORES" \
--rootfs "${CT_STORAGE}:${CT_DISK}" \
--net0 "name=eth0,bridge=${CT_BRIDGE},ip=${CT_IP},gw=${CT_GW}" \
--nameserver "$CT_NAMESERVER" \
--unprivileged 0 \
--features nesting=1 \
--start 1 \
--onboot 1

# μ»¨ν…Œμ΄λ„ˆ μ‹œμž‘ λŒ€κΈ°
info " μ»¨ν…Œμ΄λ„ˆ λΆ€νŒ… λŒ€κΈ° 쀑..."
local wait_count=0
while ! pct exec "$CTID" -- true &>/dev/null; do
sleep 2
wait_count=$((wait_count + 1))
if [ $wait_count -ge 15 ]; then
err "μ»¨ν…Œμ΄λ„ˆ μ‹œμž‘ μ‹œκ°„ 초과 (30초)"
exit 1
fi
done

log " μ»¨ν…Œμ΄λ„ˆ 생성 및 μ‹œμž‘ μ™„λ£Œ"
}

# ─── [3/6] μ˜μ‘΄μ„± μ„€μΉ˜ ────────────────────────────────────────────────
install_dependencies() {
log "[3/6] μ˜μ‘΄μ„± μ„€μΉ˜ 쀑..."

pct exec "$CTID" -- bash -c "
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq curl ca-certificates gnupg lsb-release >/dev/null 2>&1
"
info " κΈ°λ³Έ νŒ¨ν‚€μ§€ μ„€μΉ˜ μ™„λ£Œ"

# Docker μ„€μΉ˜
pct exec "$CTID" -- bash -c "
export DEBIAN_FRONTEND=noninteractive
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo \"deb [arch=\$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \$(lsb_release -cs) stable\" > /etc/apt/sources.list.d/docker.list
apt-get update -qq
apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin >/dev/null 2>&1
systemctl enable docker
systemctl start docker
"
info " Docker μ„€μΉ˜ μ™„λ£Œ"
}

# ─── [4/6] λ‘œμΌ€μΌ μ„€μ • ────────────────────────────────────────────────
setup_locale() {
log "[4/6] λ‘œμΌ€μΌ μ„€μ • 쀑..."

pct exec "$CTID" -- bash -c "
export DEBIAN_FRONTEND=noninteractive
apt-get install -y -qq locales >/dev/null 2>&1
sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
locale-gen >/dev/null 2>&1
update-locale LANG=en_US.UTF-8
"

log " λ‘œμΌ€μΌ μ„€μ • μ™„λ£Œ (en_US.UTF-8)"
}

# ─── [5/6] HeyForm 배포 ──────────────────────────────────────────────
deploy_heyform() {
log "[5/6] HeyForm 배포 쀑..."

# μ‹œν¬λ¦Ώ ν‚€ 생성
local session_key
local encryption_key
session_key=$(openssl rand -hex 32)
encryption_key=$(openssl rand -hex 32)

info " SESSION_KEY, FORM_ENCRYPTION_KEY μžλ™ 생성됨"

# μž‘μ—… 디렉토리 생성
pct exec "$CTID" -- mkdir -p /opt/heyform

# docker-compose.yml 생성
cat <<COMPOSE_EOF | pct exec "$CTID" -- tee /opt/heyform/docker-compose.yml >/dev/null
services:
heyform:
image: heyform/community-edition:latest
container_name: heyform
restart: unless-stopped
depends_on:
mongo:
condition: service_started
keydb:
condition: service_started
environment:
APP_HOMEPAGE_URL: "${APP_HOMEPAGE_URL}"
SESSION_KEY: "${session_key}"
FORM_ENCRYPTION_KEY: "${encryption_key}"
MONGO_URI: "mongodb://mongo:27017/heyform"
REDIS_HOST: keydb
REDIS_PORT: "6379"
volumes:
- ./assets:/app/static/upload
ports:
- "${HEYFORM_PORT}:8000"

mongo:
image: percona/percona-server-mongodb:4.4
container_name: heyform-mongo
restart: unless-stopped
volumes:
- ./database:/data/db

keydb:
image: eqalpha/keydb:latest
container_name: heyform-keydb
restart: unless-stopped
volumes:
- ./keydb:/data
COMPOSE_EOF

info " docker-compose.yml 생성 μ™„λ£Œ"

# Docker Compose μ‹€ν–‰
pct exec "$CTID" -- bash -c "cd /opt/heyform && docker compose pull -q 2>/dev/null && docker compose up -d"

log " HeyForm μ»¨ν…Œμ΄λ„ˆ μ‹œμž‘λ¨"
}

# ─── [6/6] ν—¬μŠ€μ²΄ν¬ ───────────────────────────────────────────────────
health_check() {
log "[6/6] ν—¬μŠ€μ²΄ν¬ μ§„ν–‰ 쀑..."

local max_attempts=30
local attempt=0

while [ $attempt -lt $max_attempts ]; do
attempt=$((attempt + 1))
if pct exec "$CTID" -- curl -sf -o /dev/null "http://localhost:${HEYFORM_PORT}" 2>/dev/null; then
log " ν—¬μŠ€μ²΄ν¬ 톡과 (${attempt}/${max_attempts})"
return 0
fi
info " λŒ€κΈ° 쀑... (${attempt}/${max_attempts})"
sleep 5
done

err "ν—¬μŠ€μ²΄ν¬ μ‹€νŒ¨ β€” ${max_attempts}회 μ‹œλ„ 후에도 응닡 μ—†μŒ"
err "μˆ˜λ™ 확인: pct exec ${CTID} -- docker compose -f /opt/heyform/docker-compose.yml logs"
exit 1
}

# ─── μ„€μΉ˜ μ™„λ£Œ μš”μ•½ ───────────────────────────────────────────────────
print_summary() {
echo ""
echo -e "${GREEN}════════════════════════════════════════════════════════════${NC}"
echo -e "${GREEN} HeyForm μ„€μΉ˜ μ™„λ£Œ${NC}"
echo -e "${GREEN}════════════════════════════════════════════════════════════${NC}"
echo ""
echo -e " CTID: ${BLUE}${CTID}${NC}"
echo -e " Hostname: ${BLUE}${CT_HOSTNAME}${NC}"
echo -e " IP: ${BLUE}${CT_IP}${NC}"
echo -e " 접속 URL: ${BLUE}${APP_HOMEPAGE_URL}${NC}"
echo -e " 포트: ${BLUE}${HEYFORM_PORT}${NC}"
echo ""
echo -e " 관리 λͺ…λ Ήμ–΄:"
echo -e " 둜그: ${YELLOW}pct exec ${CTID} -- docker compose -f /opt/heyform/docker-compose.yml logs -f${NC}"
echo -e " μž¬μ‹œμž‘: ${YELLOW}pct exec ${CTID} -- docker compose -f /opt/heyform/docker-compose.yml restart${NC}"
echo -e " 쀑지: ${YELLOW}pct exec ${CTID} -- docker compose -f /opt/heyform/docker-compose.yml down${NC}"
echo ""
echo -e "${GREEN}════════════════════════════════════════════════════════════${NC}"
}

# ─── main ─────────────────────────────────────────────────────────────
main() {
echo ""
echo -e "${GREEN}HeyForm LXC μ„€μΉ˜ 슀크립트${NC}"
echo -e "${GREEN}https://github.com/heyform/heyform${NC}"
echo ""

# Proxmox 호슀트 확인
if ! command -v pct &>/dev/null; then
err "이 μŠ€ν¬λ¦½νŠΈλŠ” Proxmox VE ν˜ΈμŠ€νŠΈμ—μ„œ μ‹€ν–‰ν•΄μ•Ό ν•©λ‹ˆλ‹€."
exit 1
fi

validate_inputs
download_template
create_container
install_dependencies
setup_locale
deploy_heyform
health_check
print_summary
}

main "$@"
Loading