Files
Ptero-Node-Builder/install.sh
2026-01-13 23:22:26 +00:00

329 lines
9.3 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
#######################################
# STATE TRACKING
#######################################
STATE_FILE="/var/log/pterodactyl_node_state"
mkdir -p $(dirname "$STATE_FILE")
touch "$STATE_FILE"
mark_done() {
echo "$1" >> "$STATE_FILE"
}
is_done() {
grep -qx "$1" "$STATE_FILE" || false
}
save_var() {
echo "$1=$2" >> "$STATE_FILE"
}
load_var() {
grep "^$1=" "$STATE_FILE" | cut -d= -f2
}
#######################################
# CONFIG
#######################################
PANEL_URL="https://panel.amslabs.net"
PTERO_API_KEY="ptla_1qpEnNZxXFAPAvjJrtRdlX5oRaDWYBImKuizXMzYne1"
PTERO_LOCATION_ID=1
CLOUDFLARE_API_TOKEN="tIrFGHOC40BuhlAsRKJYWLm-k0SH9fsnGFoKEqI4"
CLOUDFLARE_ZONE_ID="902bc1958422630ff8ce974e84af3a05"
DOMAIN="amslabs.net"
PUBLIC_IP="121.99.242.84"
DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/1460095439930916915/PJiafXkssc4NyLXdCAGGdvGxwpo93z4Gso_LBSGKKX_E9k0CDm8Uq2dbTtYXNJzXcJHN"
HOSTS_TEMPLATE="/etc/cloud/templates/hosts.debian.tmpl"
HOSTS_FILE="/etc/hosts"
WINGS_BIN="/usr/local/bin/wings"
WINGS_DIR="/etc/pterodactyl"
#######################################
# INPUTS
#######################################
if ! is_done "input_hostname"; then
read -rp "Enter host name (e.g. node03): " HOSTNAME
save_var "HOSTNAME" "$HOSTNAME"
else
HOSTNAME=$(load_var HOSTNAME)
fi
FQDN="${HOSTNAME}.${DOMAIN}"
if ! is_done "daemon_ports"; then
read -rp "Enter Wings daemon listen port (e.g. 8082): " DAEMON_LISTEN
read -rp "Enter Wings daemon SFTP port (e.g. 2024): " DAEMON_SFTP
save_var "DAEMON_LISTEN" "$DAEMON_LISTEN"
save_var "DAEMON_SFTP" "$DAEMON_SFTP"
else
DAEMON_LISTEN=$(load_var DAEMON_LISTEN)
DAEMON_SFTP=$(load_var DAEMON_SFTP)
fi
if ! is_done "resources"; then
echo "Select memory option:"
echo "1) 32768 MB"
echo "2) 65536 MB"
echo "3) 92160 MB"
read -rp "Choice [1-3]: " MEM_CHOICE
case "$MEM_CHOICE" in
1) MEMORY=32768 ;;
2) MEMORY=65536 ;;
3) MEMORY=92160 ;;
*) MEMORY=65536 ;;
esac
echo "Select disk option:"
echo "1) 32768 MB"
echo "2) 65536 MB"
echo "3) 92160 MB"
read -rp "Choice [1-3]: " DISK_CHOICE
case "$DISK_CHOICE" in
1) DISK=32768 ;;
2) DISK=65536 ;;
3) DISK=92160 ;;
*) DISK=65536 ;;
esac
save_var "MEMORY" "$MEMORY"
save_var "DISK" "$DISK"
else
MEMORY=$(load_var MEMORY)
DISK=$(load_var DISK)
fi
echo "🚀 Bootstrapping ${FQDN}"
#######################################
# HOSTS TEMPLATE
#######################################
if ! is_done "hosts_template"; then
sudo tee "${HOSTS_TEMPLATE}" >/dev/null <<EOF
127.0.0.1 localhost
10.30.40.101 node01.amslabs.net
10.30.40.101 panel.amslabs.net
10.30.40.102 node02.amslabs.net
10.30.40.103 ${FQDN}
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
EOF
sudo cp "${HOSTS_TEMPLATE}" "${HOSTS_FILE}"
mark_done "hosts_template"
fi
#######################################
# CLOUDFLARE DNS
#######################################
if ! is_done "cloudflare_dns"; then
echo "🌐 Updating Cloudflare DNS"
RECORD_ID=$(curl -s \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
"https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/dns_records?type=A&name=${FQDN}" \
| jq -r '.result[0].id // empty')
DNS_PAYLOAD=$(jq -n \
--arg name "$FQDN" \
--arg ip "$PUBLIC_IP" \
'{type:"A",name:$name,content:$ip,ttl:120,proxied:false}')
if [[ -n "$RECORD_ID" ]]; then
curl -s -X PUT \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "$DNS_PAYLOAD" \
"https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/dns_records/${RECORD_ID}" >/dev/null
else
curl -s -X POST \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "$DNS_PAYLOAD" \
"https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/dns_records" >/dev/null
fi
mark_done "cloudflare_dns"
fi
#######################################
# PACKAGES
#######################################
if ! is_done "packages"; then
sudo apt update
sudo apt install -y curl jq docker.io certbot python3-certbot-dns-cloudflare
sudo systemctl enable docker --now
mark_done "packages"
fi
#######################################
# CREATE NODE
#######################################
if ! is_done "node_created"; then
echo "📦 Creating node in panel"
NODE_PAYLOAD=$(jq -n \
--arg name "$HOSTNAME" \
--arg fqdn "$FQDN" \
--argjson location "$PTERO_LOCATION_ID" \
--argjson memory "$MEMORY" \
--argjson disk "$DISK" \
--argjson daemon_listen "$DAEMON_LISTEN" \
--argjson daemon_sftp "$DAEMON_SFTP" \
'{
name: $fqdn,
location_id: $location,
fqdn: $fqdn,
scheme: "https",
memory: $memory,
memory_overallocate: 0,
disk: $disk,
disk_overallocate: 0,
daemon_listen: $daemon_listen,
daemon_sftp: $daemon_sftp,
daemon_base: "/var/lib/pterodactyl/volumes",
upload_size: 100
}')
NODE_RESPONSE=$(curl -s -X POST \
"${PANEL_URL}/api/application/nodes" \
-H "Authorization: Bearer ${PTERO_API_KEY}" \
-H "Accept: Application/vnd.pterodactyl.v1+json" \
-H "Content-Type: application/json" \
--data "$NODE_PAYLOAD")
echo "📄 Node creation response:"
echo "$NODE_RESPONSE" | jq
NODE_ID=$(echo "$NODE_RESPONSE" | jq -r '.attributes.id // empty')
if [[ -z "$NODE_ID" ]]; then
echo "❌ Node creation failed, check API key, permissions, and values."
exit 1
fi
save_var "NODE_ID" "$NODE_ID"
mark_done "node_created"
fi
#######################################
# INSTALL WINGS
#######################################
if ! is_done "wings_installed"; then
sudo mkdir -p "${WINGS_DIR}"
curl -Lo "${WINGS_BIN}" https://github.com/pterodactyl/wings/releases/latest/download/wings_linux_amd64
sudo chmod +x "${WINGS_BIN}"
sudo tee /etc/systemd/system/wings.service >/dev/null <<EOF
[Unit]
Description=Pterodactyl Wings Daemon
After=docker.service
[Service]
User=root
Restart=always
ExecStart=${WINGS_BIN}
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
mark_done "wings_installed"
fi
#######################################
# FETCH CONFIG FROM PANEL
#######################################
if ! is_done "node_deployed"; then
echo "🧠 Fetching Wings config.yml from Panel"
NODE_ID=$(load_var NODE_ID)
for i in {1..3}; do
CONFIG_RESPONSE=$(curl -s -X GET \
"${PANEL_URL}/api/application/nodes/${NODE_ID}/configuration" \
-H "Authorization: Bearer ${PTERO_API_KEY}" \
-H "Accept: Application/vnd.pterodactyl.v1+json")
CONFIG=$(echo "$CONFIG_RESPONSE" | jq '.')
if [[ -n "$CONFIG" ]]; then
echo "$CONFIG" | sudo tee "${WINGS_DIR}/config.yml" >/dev/null
sudo chmod 600 "${WINGS_DIR}/config.yml"
mark_done "node_deployed"
echo "✅ Fetched config.yml successfully"
break
else
echo "❌ Failed to fetch config, attempt $i/3"
sleep 5
fi
done
if [[ ! -f "${WINGS_DIR}/config.yml" ]]; then
echo "❌ Could not fetch config.yml from Panel"
echo "$CONFIG_RESPONSE" | jq
exit 1
fi
fi
#######################################
# CERTBOT
#######################################
if ! is_done "certbot_done"; then
sudo mkdir -p /root/.secrets/certbot
sudo tee /root/.secrets/certbot/cloudflare.ini >/dev/null <<EOF
dns_cloudflare_api_token = ${CLOUDFLARE_API_TOKEN}
EOF
sudo chmod 600 /root/.secrets/certbot/cloudflare.ini
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /root/.secrets/certbot/cloudflare.ini \
--dns-cloudflare-propagation-seconds 30 \
-d "${FQDN}" \
--agree-tos --non-interactive \
-m admin@${DOMAIN}
mark_done "certbot_done"
fi
#######################################
# CERT RENEW HOOK
#######################################
if ! is_done "cert_hook"; then
sudo tee /usr/local/bin/wings-cert-renew.sh >/dev/null <<EOF
#!/usr/bin/env bash
systemctl restart wings
curl -X POST -H "Content-Type: application/json" \
-d "{\"content\":\"🔁 Wings restarted after cert renewal on $(hostname)\"}" \
"${DISCORD_WEBHOOK_URL}"
EOF
sudo chmod +x /usr/local/bin/wings-cert-renew.sh
sudo ln -sf /usr/local/bin/wings-cert-renew.sh \
/etc/letsencrypt/renewal-hooks/deploy/wings-restart.sh
mark_done "cert_hook"
fi
#######################################
# START WINGS
#######################################
if ! is_done "wings_started"; then
sudo systemctl enable wings
sudo systemctl restart wings
mark_done "wings_started"
fi
#######################################
# FINAL NOTIFY
#######################################
if ! is_done "final_notify"; then
curl -X POST -H "Content-Type: application/json" \
-d "{\"content\":\"✅ Pterodactyl node ${FQDN} deployed and online\"}" \
"${DISCORD_WEBHOOK_URL}"
mark_done "final_notify"
fi
echo "🎉 BOOTSTRAP COMPLETE"