Add install.sh
This commit is contained in:
329
install.sh
Normal file
329
install.sh
Normal file
@@ -0,0 +1,329 @@
|
||||
#!/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"
|
||||
Reference in New Issue
Block a user