#!/usr/bin/env bash variables() { NSAPP=$(echo "$APP" | tr '[:upper:]' '[:lower:]' | tr -d ' ') var_install="${NSAPP}-install" INTEGER='^[0-9]+([.][0-9]+)?$' PVEHOST_NAME=$(hostname) METHOD="${METHOD:-default}" if [[ "$OSTYPE" == "darwin"* ]]; then RANDOM_UUID="$(uuidgen | tr '[:upper:]' '[:lower:]')" else RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)" fi EXECUTION_ID="${RANDOM_UUID}" SESSION_ID="${RANDOM_UUID:0:8}" if [[ "$OSTYPE" == "darwin"* ]]; then INSTALL_LOG="/tmp/dynr/INSTALL_LOG_${NSAPP}.log" else INSTALL_LOG="/opt/dynr/INSTALL_LOG_${NSAPP}.log" fi CTTYPE="${CTTYPE:-${CT_TYPE:-1}}" parse_dev_mode if [[ "${DEV_MODE_LOGS:-false}" == "true" ]]; then if [[ "$OSTYPE" == "darwin"* ]]; then mkdir -p /tmp/dynr BUILD_LOG="/tmp/dynr/create-lxc-${SESSION_ID}-$(date +%Y%m%d_%H%M%S).log" else mkdir -p /var/log/dynr BUILD_LOG="/var/log/dynr/create-lxc-${SESSION_ID}-$(date +%Y%m%d_%H%M%S).log" fi fi if command -v pveversion >/dev/null 2>&1; then PVEVERSION="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')" else PVEVERSION="N/A" fi KERNEL_VERSION=$(uname -r) if [[ -n "${var_cpu:-}" ]] && [[ "${var_cpu}" =~ ^[0-9]+$ ]]; then export APP_DEFAULT_CPU="${var_cpu}" fi if [[ -n "${var_ram:-}" ]] && [[ "${var_ram}" =~ ^[0-9]+$ ]]; then export APP_DEFAULT_RAM="${var_ram}" fi if [[ -n "${var_disk:-}" ]] && [[ "${var_disk}" =~ ^[0-9]+$ ]]; then export APP_DEFAULT_DISK="${var_disk}" fi if [[ "$OSTYPE" == "darwin"* ]]; then mkdir -p /tmp/dynr 2>/dev/null else mkdir -p /opt/dynr 2>/dev/null fi } DYNR_CORE="https://git.dynastyrevolution.com/DYNR/DynastyRevolution-Scripts/raw/branch/main/core" DYNR_REPO="https://git.dynastyrevolution.com/DYNR/DynastyRevolution-Scripts/raw/branch/main" eval "$(curl -fsSL ${DYNR_CORE}/api.func)" eval "$(curl -fsSL ${DYNR_CORE}/core.func)" eval "$(curl -fsSL ${DYNR_CORE}/error_handler.func)" network_check() { msg_info "Verifying Fleet Uplink (1.1.1.1 / 1.0.0.1)..." if ping -c 1 -W 2 1.1.1.1 &>/dev/null || ping -c 1 -W 2 1.0.0.1 &>/dev/null; then msg_ok "Network OK." else msg_error "Network Unreachable. Provision Aborted." exit 1 fi } github_api_check() { msg_info "Checking Github API Token..." local api_response=$(curl -s https://api.github.com/rate_limit) local remaining=$(echo "$api_response" | grep -A 2 '"core"' | grep '"remaining"' | tr -dc '0-9') if [[ -n "$remaining" ]]; then msg_ok "Github: Tokens Remaining: ${GOLD}${remaining}${NC}" else msg_warn "Github: API Limit Unreachable. Proceeding with caution." fi } check_manifest_updates() { local remote_script="${DYNR_REPO}/${MODULE_PATH}" local remote_ver=$(curl -fsSL --connect-timeout 2 "$remote_script" | grep -m 1 "VERSION=" | cut -d'"' -f2 || echo "0") if [[ "$remote_ver" > "$VERSION" ]]; then msg_warn "A New update has Dawned: v$remote_ver is available." msg_info "Current Version: v$VERSION" echo -e "${GOLD}Recommendation: Re-sync with latest version.${NC}" echo "" sleep 2 fi } remote_strike_ui() { local title="Dynasty Remote Provisioning" local prompt="$1" local default="$2" if command -v osascript &>/dev/null && [[ "$OSTYPE" == "darwin"* ]]; then osascript -e "display dialog \"$prompt\" default answer \"$default\" with title \"$title\" buttons {\"Cancel\", \"Next\"} default button \"Next\"" -e "text returned of result" 2>/dev/null elif command -v whiptail &>/dev/null; then whiptail --backtitle "$title" --title "TACTICAL INPUT" --inputbox "$prompt" 10 58 "$default" 3>&1 1>&2 2>&3 else read -p "$prompt [$default]: " val /dev/null || echo 0) if [[ "$per_user_maxkeys" -eq 0 ]]; then msg_error "Kernel parameter read failure." exit 107 fi } validate_container_id() { local ctid="$1" if ! [[ "$ctid" =~ ^[0-9]+$ ]]; then return 1 fi if command -v pvesh &>/dev/null; then local cluster_ids=$(pvesh get /cluster/resources --type vm --output-format json 2>/dev/null | grep -oP '"vmid":\s*\K[0-9]+' 2>/dev/null || true) if [[ -n "$cluster_ids" ]] && echo "$cluster_ids" | grep -qw "$ctid"; then return 1 fi fi if [[ -f "/etc/pve/lxc/${ctid}.conf" ]] || [[ -f "/etc/pve/qemu-server/${ctid}.conf" ]]; then return 1 fi return 0 } get_valid_container_id() { local suggested_id="${1:-$(pvesh get /cluster/nextid 2>/dev/null || echo 100)}" while ! validate_container_id "$suggested_id"; do suggested_id=$((suggested_id + 1)) done echo "$suggested_id" } select_storage() { local CLASS=$1 local CONTENT='rootdir' if [[ "$CLASS" == "template" ]]; then CONTENT='vztmpl' fi declare -A STORAGE_MAP local -a MENU=() while read -r TAG TYPE _ TOTAL USED FREE _; do if [[ -z "$TAG" ]] || [[ -z "$TYPE" ]]; then continue; fi if [[ "$TYPE" == "pbs" ]] && [[ "$CLASS" == "container" ]]; then continue; fi local DISPLAY="${TAG} (${TYPE})" local FREE_FMT=$(numfmt --to=iec --from-unit=1024 --format %.1f <<<"$FREE") STORAGE_MAP["$DISPLAY"]="$TAG" MENU+=("$DISPLAY" "Free: ${FREE_FMT}B" "OFF") done < <(pvesm status -content "$CONTENT" | awk 'NR>1') if [[ $((${#MENU[@]} / 3)) -eq 0 ]]; then msg_error "No valid storage found. (Note: PBS is for backups only)" exit 1 fi if [[ $((${#MENU[@]} / 3)) -eq 1 ]]; then STORAGE_RESULT="${STORAGE_MAP[${MENU[0]}]}" return 0 fi local selected_display=$(whiptail --backtitle "Dynasty OS" --title "STANCE" --radiolist "Select Storage Pool:" 16 60 6 "${MENU[@]}" 3>&1 1>&2 2>&3) STORAGE_RESULT="${STORAGE_MAP[$selected_display]}" } base_settings() { CT_TYPE=${var_unprivileged:-"1"} DISK_SIZE="${var_disk:-8}" CORE_COUNT="${var_cpu:-1}" RAM_SIZE="${var_ram:-1024}" CT_ID=$(get_valid_container_id "${var_ctid:-$NEXTID}") HN="${var_hostname:-$NSAPP}" BRG=${var_brg:-"vmbr0"} NET=${var_net:-"dhcp"} TAGS="dynasty-script,${var_tags:-}" VLAN_TAG="" ENABLE_TUN="0" ENABLE_SSH="0" OS_CHOICE="${var_os:-debian}" OS_VERSION="${var_version:-12}" } advanced_settings() { tput smcup 2>/dev/null || true trap 'tput rmcup 2>/dev/null || true' RETURN local STEP=1 local MAX_STEP=13 while [[ $STEP -le $MAX_STEP ]]; do case $STEP in 1) if result=$(whiptail --backtitle "Dynasty OS" --title "STANCE" --radiolist "Select Container Type:" 12 58 2 "1" "Tactical (Unprivileged)" ON "0" "LXC (Privileged)" OFF 3>&1 1>&2 2>&3); then CT_TYPE="$result"; STEP=$((STEP + 1)); else exit_script; fi ;; 2) if result=$(whiptail --backtitle "Dynasty OS" --title "ERA SELECTION" --radiolist "Select OS Base:" 12 58 3 "debian" "Debian 12" ON "alpine" "Alpine 3.20" OFF "ubuntu" "Ubuntu 24.04" OFF 3>&1 1>&2 2>&3); then OS_CHOICE="$result"; STEP=$((STEP + 1)); else STEP=$((STEP - 1)); fi ;; 3) if result=$(whiptail --backtitle "Dynasty OS" --title "AUTHORITY" --passwordbox "Establish Root Password (Blank for Auto-Auth):" 12 58 3>&1 1>&2 2>&3); then if [[ -n "$result" ]]; then PW="--password $result"; else PW=""; fi; STEP=$((STEP + 1)); else STEP=$((STEP - 1)); fi ;; 4) if whiptail --backtitle "Dynasty OS" --title "VPN CAPABILITY" --yesno "Enable TUN/TAP (/dev/net/tun) for VPN interfaces?" 10 58; then ENABLE_TUN="1"; else ENABLE_TUN="0"; fi; STEP=$((STEP + 1)) ;; 5) if whiptail --backtitle "Dynasty OS" --title "SSH ACCESS" --yesno "Enable Root SSH Access post-install?" 10 58; then ENABLE_SSH="1"; else ENABLE_SSH="0"; fi; STEP=$((STEP + 1)) ;; 6) if result=$(whiptail --backtitle "Dynasty OS" --title "NODE ID" --inputbox "Confirm ID:" 10 58 "$CT_ID" 3>&1 1>&2 2>&3); then CT_ID="$result"; STEP=$((STEP + 1)); else STEP=$((STEP - 1)); fi ;; 7) if result=$(whiptail --backtitle "Dynasty OS" --title "DESIGNATION" --inputbox "Set Hostname:" 10 58 "$HN" 3>&1 1>&2 2>&3); then HN="$result"; STEP=$((STEP + 1)); else STEP=$((STEP - 1)); fi ;; 8) if result=$(whiptail --backtitle "Dynasty OS" --title "CAPACITY" --inputbox "Storage (GB):" 10 58 "$DISK_SIZE" 3>&1 1>&2 2>&3); then DISK_SIZE="$result"; STEP=$((STEP + 1)); else STEP=$((STEP - 1)); fi ;; 9) if result=$(whiptail --backtitle "Dynasty OS" --title "CORES" --inputbox "CPU Allocation:" 10 58 "$CORE_COUNT" 3>&1 1>&2 2>&3); then CORE_COUNT="$result"; STEP=$((STEP + 1)); else STEP=$((STEP - 1)); fi ;; 10) if result=$(whiptail --backtitle "Dynasty OS" --title "MEMORY" --inputbox "RAM (MiB):" 10 58 "$RAM_SIZE" 3>&1 1>&2 2>&3); then RAM_SIZE="$result"; STEP=$((STEP + 1)); else STEP=$((STEP - 1)); fi ;; 11) if result=$(whiptail --backtitle "Dynasty OS" --title "NETWORK" --inputbox "VLAN Tag (Leave blank for none):" 10 58 "${VLAN_TAG:-}" 3>&1 1>&2 2>&3); then VLAN_TAG="$result"; STEP=$((STEP + 1)); else STEP=$((STEP - 1)); fi ;; 12) if select_storage container; then CONTAINER_STORAGE="$STORAGE_RESULT"; STEP=$((STEP + 1)); else STEP=$((STEP - 1)); fi ;; 13) if whiptail --backtitle "Dynasty OS" --title "CONFIRMATION" --yesno "Manifest Node $APP?" 10 58; then STEP=$((STEP + 1)); else STEP=$((STEP - 1)); fi ;; esac done tput rmcup 2>/dev/null || true trap - RETURN } build_container() { if [[ "$OS_CHOICE" == "debian" ]]; then OS_VERSION="12"; fi if [[ "$OS_CHOICE" == "alpine" ]]; then OS_VERSION="3.20"; fi if [[ "$OS_CHOICE" == "ubuntu" ]]; then OS_VERSION="24.04"; fi msg_info "Locating Files for $OS_CHOICE" local template_search="${OS_CHOICE}-${OS_VERSION}" local template=$(pveam available -section system | grep "$template_search" | awk '{print $2}' | tail -n1) if [[ -z "$template" ]]; then msg_error "Files not found in system records." exit 1 fi if ! pveam list local | grep -q "$template"; then pveam download local "$template" $STD fi msg_info "Welding Structure" local VLAN_OPT="" if [[ -n "$VLAN_TAG" ]]; then VLAN_OPT=",tag=$VLAN_TAG" fi pct create "$CT_ID" "local:vztmpl/$template" \ -hostname "$HN" \ -net0 name=eth0,bridge="$BRG",ip="$NET"${VLAN_OPT} \ -cores "$CORE_COUNT" \ -memory "$RAM_SIZE" \ -rootfs "${CONTAINER_STORAGE:-local}":"$DISK_SIZE" \ -unprivileged "$CT_TYPE" \ -features nesting=1 \ -onboot 1 \ ${PW:-} \ $STD if [[ "$ENABLE_TUN" == "1" ]]; then echo "lxc.cgroup2.devices.allow: c 10:200 rwm" >> /etc/pve/lxc/${CT_ID}.conf echo "lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file" >> /etc/pve/lxc/${CT_ID}.conf fi pct start "$CT_ID" msg_info "Awaiting Network ..." sleep 3 IP=$(pct exec "$CT_ID" ip -4 addr show eth0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -n1) if [[ "$ENABLE_SSH" == "1" ]]; then msg_info "Enabling Secure Shell (SSH)..." if [[ "$OS_CHOICE" == "alpine" ]]; then pct exec "$CT_ID" -- sh -c "apk add openssh && rc-update add sshd && service sshd start && sed -i 's/#PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config && service sshd restart" >/dev/null 2>&1 else pct exec "$CT_ID" -- sh -c "apt-get update >/dev/null 2>&1 && apt-get install -y openssh-server >/dev/null 2>&1 && sed -i 's/#PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config && systemctl restart sshd" >/dev/null 2>&1 fi fi } echo_default() { msg_info "Deploying with Defaults for ${GOLD}${app_name}${NC}:" echo -e " ${BLUE}OS Version:${NC} ${GOLD}${OS_CHOICE} ${OS_VERSION}${NC}" echo -e " ${BLUE}Resources:${NC} ${GOLD}${CORE_COUNT} Cores, ${RAM_SIZE}MB RAM, ${DISK_SIZE}GB Storage${NC}" echo -e " ${BLUE}Network:${NC} ${GOLD}${NET} on ${BRG}${NC}" echo -e " ${BLUE}ID & Name:${NC} ${GOLD}LXC ${CT_ID} (${HN})${NC}" echo "" sleep 2 } description() { local portal="http://${IP}:${PORT:-0000}" local DESCRIPTION="

DYNASTY REVOLUTION

${APP} PROVISIONED

" pct set "$CT_ID" -description "$DESCRIPTION" >/dev/null 2>&1 echo -e "\n${GOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e " ${GOLD}${BOLD}PROVISION COMPLETE${NC}" echo -e " ${BLUE}Access: ${NC} ${GOLD}${portal}${NC}" if [[ "$ENABLE_SSH" == "1" ]]; then echo -e " ${BLUE}SSH Authority:${NC} ${GOLD}ssh root@${IP}${NC}" fi echo -e " ${BLUE}Log Path:${NC} ${GOLD}${INSTALL_LOG}${NC}" echo -e "${GOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" } start() { variables if command -v pveversion >/dev/null 2>&1; then header_info network_check github_api_check check_manifest_updates NEXTID=$(pvesh get /cluster/nextid) base_settings if [[ "$METHOD" == "advanced" ]]; then advanced_settings; else echo_default; fi build_container else initiate_remote_strike fi }