summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LICENSE9
-rw-r--r--README.md86
-rwxr-xr-xcheck-hardened.sh37
-rwxr-xr-xcheck_rdns_retry.sh27
-rwxr-xr-xfunctions/destroy_vps_by_label.sh28
-rwxr-xr-xfunctions/disable_backups_by_label.sh23
-rw-r--r--functions/disable_ip.sh18
-rwxr-xr-xfunctions/enable_backups_by_label.sh23
-rwxr-xr-xfunctions/list_all_vps.sh9
-rwxr-xr-xfunctions/provision.sh135
-rwxr-xr-xfunctions/reboot_vps.sh7
-rwxr-xr-xfunctions/resize_vps.sh27
-rwxr-xr-xfunctions/safe_create_dataset.sh12
-rwxr-xr-xfunctions/status_vps.sh8
-rwxr-xr-xfunctions/usage.sh22
-rwxr-xr-xfunctions/verify_ptr.sh29
-rwxr-xr-xgenesis_squeaky.sh44
-rwxr-xr-xgenesisctl.sh104
-rw-r--r--vps/.env5
-rwxr-xr-xvps/check-hardened.sh37
-rwxr-xr-xvps/check_rdns_retry.sh27
-rwxr-xr-xvps/functions/destroy_vps_by_label.sh28
-rwxr-xr-xvps/functions/disable_backups_by_label.sh23
-rw-r--r--vps/functions/disable_ip.sh18
-rwxr-xr-xvps/functions/enable_backups_by_label.sh23
-rwxr-xr-xvps/functions/list_all_vps.sh9
-rwxr-xr-xvps/functions/provision.sh135
-rwxr-xr-xvps/functions/reboot_vps.sh7
-rwxr-xr-xvps/functions/resize_vps.sh27
-rwxr-xr-xvps/functions/safe_create_dataset.sh12
-rwxr-xr-xvps/functions/status_vps.sh8
-rwxr-xr-xvps/functions/usage.sh22
-rwxr-xr-xvps/functions/verify_ptr.sh29
-rwxr-xr-xvps/genesis_squeaky.sh44
-rwxr-xr-xvps/genesisctl.sh104
35 files changed, 1206 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6f2010e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,9 @@
+MIT License
+
+Copyright (c) 2025 doctator
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8c0b26e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,86 @@
+# FailZero
+
+**FailZero** is a disaster resilience and ransomware recovery platform built by Genesis Hosting Technologies.
+
+We’re not here to sell you backups — we’re here to help you survive failure.
+Whether it’s an encrypted drive, a botched update, or a full datacenter fire, FailZero ensures you have a path forward.
+
+> _"Hope is not a strategy. Recovery is."_ — FailZero Field Ops Manual
+
+---
+
+## 🛠 What It Does
+
+FailZero is designed for reactive and proactive DR (disaster recovery) workflows:
+
+- 🔁 **Live Backup & Mirroring** — Sync media, configs, and databases in near real-time
+- 🚨 **Ransomware Containment** — Immutable storage options + airgapped fallback
+- 💾 **Versioned Archives** — Roll back to any known good state
+- 🧩 **Hybrid Architecture** — Works with ZFS, MinIO, rclone, Samba, PostgreSQL, and more
+- 📦 **Disaster Drill Framework** — Simulate failure, validate recovery
+
+---
+
+## 📦 Core Components
+
+- `genesisctl` – unified CLI to manage snapshots, syncs, and alerts
+- `GenesisTimer` – hourly integrity & latency monitor with alerts
+- `dr_spl_overlay.sh` – SPL server resilience and fallback handler
+- `krang_ops_bot` – Telegram integration for all alerts and status reports
+
+---
+
+## 🔐 Supported Services
+
+FailZero DR packages are available for:
+
+- 🎙️ **StationPlaylist** (SPL)
+- 📻 **AzuraCast**
+- 🐘 **Mastodon**
+- 📁 Any self-hosted Linux application with a filesystem and heartbeat
+
+---
+
+## 🚨 Example: Ransomware Workflow
+
+1. Primary storage compromised (e.g. `/mnt/ransom`)
+2. FailZero detects integrity drift or timeout
+3. Auto-switch to mirror: `/mnt/failzero`
+4. Alerts fired via Telegram, Web, or CLI
+5. Optional: Trigger airgapped restore from cold site
+
+---
+
+## 📄 Documentation
+
+The [FailZero Field Operations Manual](docs/FailZero_FieldOps.pdf) includes:
+
+- DR runbooks
+- Mock exercise scripts
+- Deployment guides
+- Legal disclaimers
+
+---
+
+## 💬 Contact
+
+Want to onboard or simulate a failure?
+
+- Telegram: [@krang_ops_bot](https://t.me/krang_ops_bot)
+- Email: `failzero@genesishostingtechnologies.com`
+- Status: [status.genesis-radio.net](https://status.genesis-radio.net)
+
+---
+
+## 💥 Warning
+
+> **FailZero is a disaster recovery tool, not a magic wand.**
+> Regular testing, airgaps, and ops discipline are required to avoid data loss.
+
+---
+
+## License
+
+FailZero is © 2025 Genesis Hosting Technologies.
+Usage and distribution subject to terms in `LICENSE`.
+
diff --git a/check-hardened.sh b/check-hardened.sh
new file mode 100755
index 0000000..cdaeef8
--- /dev/null
+++ b/check-hardened.sh
@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+# check-hardened.sh - Scan all known Genesis VPSes for hardening status
+# Requirements: ssh access to all VPSes by label or IP
+
+LOG_BASE="/home/doc/vpslogs"
+MARKER_FILE="/var/log/genesis-hardened.ok"
+
+if [ ! -d "$LOG_BASE" ]; then
+ echo "❌ Log directory $LOG_BASE does not exist. Are you running this on Krang?"
+ exit 1
+fi
+
+cd "$LOG_BASE" || exit 1
+
+echo "🔍 Scanning for hardened Genesis VPSes..."
+echo
+
+for LOG in *.log; do
+ VPS_LABEL="${LOG%.log}"
+ LAST_KNOWN_IP=$(grep -Eo '\([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\)' "$LOG" | tail -1 | tr -d '()')
+
+ if [ -z "$LAST_KNOWN_IP" ]; then
+ echo "⚠️ $VPS_LABEL: No IP found in log. Skipping."
+ continue
+ fi
+
+ echo -n "🔧 $VPS_LABEL ($LAST_KNOWN_IP): "
+
+ ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no root@"$LAST_KNOWN_IP" "test -f $MARKER_FILE" >/dev/null 2>&1
+
+ if [ $? -eq 0 ]; then
+ echo "✅ Hardened"
+ else
+ echo "❌ Not marked as hardened"
+ fi
+
+done
diff --git a/check_rdns_retry.sh b/check_rdns_retry.sh
new file mode 100755
index 0000000..b11208b
--- /dev/null
+++ b/check_rdns_retry.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+set -e
+[ -f ".env" ] && source .env
+LOGFILE="/home/doc/vpslogs/pending_rdns.log"
+TMPFILE="/tmp/rdns_retry.log"
+
+touch "$TMPFILE"
+
+while IFS="|" read -r LINODE_ID IP LABEL; do
+ CURRENT_RDNS=$(dig -x "$IP" +short)
+ EXPECTED_RDNS="$LABEL.failzero.net."
+
+ if [[ "$CURRENT_RDNS" == "$EXPECTED_RDNS" ]]; then
+ echo "✅ $IP already has correct rDNS ($CURRENT_RDNS)"
+ else
+ echo "⏳ rDNS not set correctly for $LABEL ($IP). Retrying..."
+ RESPONSE=$(curl -s -X PUT "https://api.linode.com/v4/linode/instances/$LINODE_ID/ips/$IP" \
+ -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"rdns": "'"$LABEL.failzero.net"'"}')
+ echo "🔁 Retry result for $IP: $RESPONSE"
+ fi
+
+ echo "$LINODE_ID|$IP|$LABEL" >> "$TMPFILE"
+done < "$LOGFILE"
+
+mv "$TMPFILE" "$LOGFILE"
diff --git a/functions/destroy_vps_by_label.sh b/functions/destroy_vps_by_label.sh
new file mode 100755
index 0000000..09d807e
--- /dev/null
+++ b/functions/destroy_vps_by_label.sh
@@ -0,0 +1,28 @@
+destroy_vps_by_label() {
+ LABEL="$1"
+ echo "Looking for VPS with label '$LABEL'..."
+ LINODE_ID=$(curl -s -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ https://api.linode.com/v4/linode/instances | \
+ jq -r --arg LABEL "$LABEL" '.data[] | select(.label == $LABEL) | .id')
+
+ if [ -z "$LINODE_ID" ]; then
+ echo "Error: No Linode found with label '$LABEL'"
+ exit 1
+ fi
+
+ read -rp "Are you sure you want to destroy VPS '$LABEL' (ID: $LINODE_ID)? [y/N] " confirm
+ if [[ "$confirm" =~ ^[Yy]$ ]]; then
+ echo "Destroying Linode with ID $LINODE_ID (label: $LABEL)..."
+ HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
+ https://api.linode.com/v4/linode/instances/$LINODE_ID \
+ -H "Authorization: Bearer $LINODE_API_TOKEN")
+
+ if [[ "$HTTP_STATUS" == "204" ]]; then
+ echo "✅ Linode $LABEL (ID $LINODE_ID) has been destroyed."
+ else
+ echo "❌ Failed to destroy VPS. HTTP status: $HTTP_STATUS"
+ fi
+ else
+ echo "Cancelled. VPS '$LABEL' not destroyed."
+ fi
+}
diff --git a/functions/disable_backups_by_label.sh b/functions/disable_backups_by_label.sh
new file mode 100755
index 0000000..417bdb8
--- /dev/null
+++ b/functions/disable_backups_by_label.sh
@@ -0,0 +1,23 @@
+disable_backups_by_label() {
+ LABEL="$1"
+ LINODE_ID=$(curl -s -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ https://api.linode.com/v4/linode/instances | \
+ jq -r --arg LABEL "$LABEL" '.data[] | select(.label == $LABEL) | .id')
+
+ if [ -z "$LINODE_ID" ]; then
+ echo "❌ No Linode found with label '$LABEL'"
+ exit 1
+ fi
+
+ echo "Disabling backups for Linode '$LABEL' (ID: $LINODE_ID)..."
+
+ HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
+ https://api.linode.com/v4/linode/instances/$LINODE_ID/backups/disable \
+ -H "Authorization: Bearer $LINODE_API_TOKEN")
+
+ if [[ "$HTTP_STATUS" == "200" ]]; then
+ echo "✅ Backups disabled for Linode $LABEL."
+ else
+ echo "❌ Failed to disable backups (HTTP $HTTP_STATUS)"
+ fi
+}
diff --git a/functions/disable_ip.sh b/functions/disable_ip.sh
new file mode 100644
index 0000000..0021b74
--- /dev/null
+++ b/functions/disable_ip.sh
@@ -0,0 +1,18 @@
+disable_ip() {
+ local ip="$1"
+
+ if [[ -z "$ip" ]]; then
+ echo "[!] No IP specified."
+ exit 1
+ fi
+
+ echo "[*] Disabling access to VPS with IP: $ip"
+
+ # Block all traffic to/from that IP via iptables
+ iptables -A INPUT -s "$ip" -j DROP
+ iptables -A OUTPUT -d "$ip" -j DROP
+
+ echo "$ip - disabled on $(date)" >> /var/log/genesis-disabled.log
+
+ echo "[✓] $ip has been blocked and logged."
+}
diff --git a/functions/enable_backups_by_label.sh b/functions/enable_backups_by_label.sh
new file mode 100755
index 0000000..08fb31d
--- /dev/null
+++ b/functions/enable_backups_by_label.sh
@@ -0,0 +1,23 @@
+enable_backups_by_label() {
+ LABEL="$1"
+ LINODE_ID=$(curl -s -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ https://api.linode.com/v4/linode/instances | \
+ jq -r --arg LABEL "$LABEL" '.data[] | select(.label == $LABEL) | .id')
+
+ if [ -z "$LINODE_ID" ]; then
+ echo "❌ No Linode found with label '$LABEL'"
+ exit 1
+ fi
+
+ echo "Enabling backups for Linode '$LABEL' (ID: $LINODE_ID)..."
+
+ HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
+ https://api.linode.com/v4/linode/instances/$LINODE_ID/backups/enable \
+ -H "Authorization: Bearer $LINODE_API_TOKEN")
+
+ if [[ "$HTTP_STATUS" == "200" ]]; then
+ echo "✅ Backups enabled for Linode $LABEL."
+ else
+ echo "❌ Failed to enable backups (HTTP $HTTP_STATUS)"
+ fi
+}
diff --git a/functions/list_all_vps.sh b/functions/list_all_vps.sh
new file mode 100755
index 0000000..8ce99eb
--- /dev/null
+++ b/functions/list_all_vps.sh
@@ -0,0 +1,9 @@
+list_all_vps() {
+ curl -s -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ https://api.linode.com/v4/linode/instances | \
+ jq -r '
+ .data[] | [.label, .id, .region, .type, .ipv4[0], .status] |
+ @tsv' | column -t -s $'\t' | \
+ awk 'BEGIN { print "LABEL ID REGION TYPE IP STATUS" }
+ { printf "%-11s %-10s %-10s %-16s %-15s %s\n", $1, $2, $3, $4, $5, $6 }'
+}
diff --git a/functions/provision.sh b/functions/provision.sh
new file mode 100755
index 0000000..f6e9d39
--- /dev/null
+++ b/functions/provision.sh
@@ -0,0 +1,135 @@
+provision_vps() {
+ LABEL="$1"
+ REGION="$2"
+ TYPE="$3"
+ IMAGE="$4"
+ ROOT_PASS="${5:-$(openssl rand -base64 16)}"
+
+ if [[ "$LINODE_API_TOKEN" == "REPLACE_WITH_YOUR_LINODE_API_TOKEN" ]]; then
+ echo "❌ Error: You must set your LINODE_API_TOKEN at the top of this script."
+ exit 1
+ fi
+
+ CLOUD_INIT=$(cat <<EOF
+#cloud-config
+hostname: genesis-vps
+manage_etc_hosts: true
+write_files:
+ - path: /usr/local/bin/genesis_squeaky.sh
+ permissions: '0755'
+ content: |
+ #!/bin/bash
+ set -e
+ GEN_HOSTNAME="genesis-vps-$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 6)"
+ LOGDIR="/home/doc/vpslogs"
+ LOGFILE="$LOGDIR/$GEN_HOSTNAME.log"
+ IP_ADDR=$(hostname -I | awk '{print $1}')
+
+ iptables -A OUTPUT -p icmp --icmp-type time-exceeded -j DROP
+ iptables -A INPUT -p udp --dport 33434:33534 -j DROP
+ iptables -A INPUT -p tcp --dport 33434:33534 -j DROP
+
+ hostnamectl set-hostname "$GEN_HOSTNAME"
+ sed -i "s/^127.0.1.1.*/127.0.1.1 $GEN_HOSTNAME/" /etc/hosts
+
+ systemctl stop linode-cloudinit 2>/dev/null || true
+ systemctl disable linode-cloudinit 2>/dev/null || true
+ touch /etc/cloud/cloud-init.disabled
+ rm -rf /etc/cloud /var/lib/cloud /var/log/cloud-init.log
+
+ rm -f /etc/motd /etc/update-motd.d/linode
+ rm -rf /usr/share/linode*
+ rm -f /etc/apt/sources.list.d/linode.list
+ apt remove --purge -y linode-cli linode-config 2>/dev/null || true
+
+ echo "[genesisctl] Attempting to log to Krang via webhook..." >> /var/log/genesis-harden.log
+ curl -s -X POST -H "Content-Type: application/json" \
+ -d "{\"host\": \"$GEN_HOSTNAME\", \"ip\": \"$IP_ADDR\", \"timestamp\": \"$(date)\"}" \
+ http://krang.core.sshjunkie.com:8080/genesislog >> /var/log/genesis-harden.log 2>&1 || echo "[genesisctl] Krang webhook logging failed" >> /var/log/genesis-harden.log
+
+ touch /var/log/genesis-hardened.ok
+
+runcmd:
+ - [ bash, /usr/local/bin/genesis_squeaky.sh ]
+EOF
+)
+
+ USER_DATA=$(echo "$CLOUD_INIT" | base64 -w 0)
+
+ echo "Provisioning VPS '$LABEL' in $REGION with type $TYPE and image $IMAGE..."
+ TMP_FILE=$(mktemp)
+ JSON_PAYLOAD=$(cat <<EOF
+{
+ "label": "$LABEL",
+ "region": "$REGION",
+ "type": "$TYPE",
+ "image": "$IMAGE",
+ "authorized_users": [],
+ "root_pass": "$ROOT_PASS",
+ "booted": true,
+ "metadata": {
+ "user_data": "$USER_DATA"
+ }
+}
+EOF
+)
+
+ HTTP_STATUS=$(curl -s -o "$TMP_FILE" -w "%{http_code}" -X POST https://api.linode.com/v4/linode/instances \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ -d "$JSON_PAYLOAD")
+
+ echo -e "\n--- HTTP STATUS: $HTTP_STATUS ---"
+ echo "--- RAW RESPONSE: ---"
+ cat "$TMP_FILE"
+
+ if [[ "$HTTP_STATUS" != "200" && "$HTTP_STATUS" != "201" ]]; then
+ echo -e "\n❌ Failed to provision VPS (HTTP $HTTP_STATUS)"
+ jq . "$TMP_FILE"
+ exit 1
+ fi
+
+ echo -e "\n✅ VPS provisioned:"
+ IP=$(jq -r '.ipv4[0]' "$TMP_FILE")
+ LINODE_ID=$(jq -r '.id' "$TMP_FILE")
+ echo "Label: $LABEL"
+ echo "IP Address: $IP"
+ echo "Root Password: $ROOT_PASS"
+
+ # Add DNS record to Cloudflare
+ echo "📡 Adding A record for $LABEL.$CF_DOMAIN → $IP..."
+ echo "[DEBUG] CF_API_TOKEN=$CF_API_TOKEN"
+ curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \
+ -H "Authorization: Bearer $CF_API_TOKEN" \
+ -H "Content-Type: application/json" \
+ --data-binary @<(cat <<JSON
+{
+ "type": "A",
+ "name": "$LABEL.$CF_DOMAIN",
+ "content": "$IP",
+ "ttl": 120,
+ "proxied": false
+}
+JSON
+) | jq '.success, .errors, .messages'
+
+ echo "⏳ Waiting indefinitely for DNS to propagate before setting rDNS..."
+i=1
+while true; do
+ CURRENT_IP=$(dig +short "$LABEL.$CF_DOMAIN")
+ if [[ "$CURRENT_IP" == "$IP" ]]; then
+ echo "✅ A record resolved. Setting rDNS..."
+ curl -s -X PUT "https://api.linode.com/v4/linode/instances/$LINODE_ID/ips/$IP" \
+ -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"rdns": "'"$LABEL.$CF_DOMAIN"'"}'
+ break
+ fi
+ echo "⏳ Attempt $i: DNS not ready. Waiting 15s..."
+ sleep 15
+ ((i++))
+done
+
+
+ echo "$LINODE_ID|$IP|$LABEL" >> /home/doc/vpslogs/pending_rdns.log
+}
diff --git a/functions/reboot_vps.sh b/functions/reboot_vps.sh
new file mode 100755
index 0000000..2741b9c
--- /dev/null
+++ b/functions/reboot_vps.sh
@@ -0,0 +1,7 @@
+reboot_vps() {
+ LINODE_ID="$1"
+ echo "Rebooting Linode VPS ID $LINODE_ID..."
+
+ curl -s -X POST https://api.linode.com/v4/linode/instances/$LINODE_ID/reboot \
+ -H "Authorization: Bearer $LINODE_API_TOKEN" | jq
+}
diff --git a/functions/resize_vps.sh b/functions/resize_vps.sh
new file mode 100755
index 0000000..c06ea91
--- /dev/null
+++ b/functions/resize_vps.sh
@@ -0,0 +1,27 @@
+resize_vps() {
+ LABEL="$1"
+ NEW_TYPE="$2"
+
+ LINODE_ID=$(curl -s -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ https://api.linode.com/v4/linode/instances | \
+ jq -r --arg LABEL "$LABEL" '.data[] | select(.label == $LABEL) | .id')
+
+ if [ -z "$LINODE_ID" ]; then
+ echo "❌ No Linode found with label '$LABEL'"
+ exit 1
+ fi
+
+ echo "Resizing Linode '$LABEL' to type '$NEW_TYPE'..."
+
+ HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ -d '{"type": "'"$NEW_TYPE"'"}' \
+ https://api.linode.com/v4/linode/instances/$LINODE_ID/resize)
+
+ if [[ "$HTTP_STATUS" == "200" ]]; then
+ echo "✅ Linode $LABEL resized to $NEW_TYPE."
+ else
+ echo "❌ Failed to resize VPS. HTTP status: $HTTP_STATUS"
+ fi
+}
diff --git a/functions/safe_create_dataset.sh b/functions/safe_create_dataset.sh
new file mode 100755
index 0000000..1960e55
--- /dev/null
+++ b/functions/safe_create_dataset.sh
@@ -0,0 +1,12 @@
+safe_create_dataset() {
+ FULLPATH="$1"
+
+ # Remove any trailing slash
+ FULLPATH="${FULLPATH%/}"
+
+ POOL="${FULLPATH%%/*}"
+ DATASET="${FULLPATH#*/}"
+
+ echo "🛰 Connecting to Shredder to safely create '${POOL}/${DATASET}'..."
+ ssh shredder "/usr/local/bin/genesis-safe-zfs.sh $POOL $DATASET"
+}
diff --git a/functions/status_vps.sh b/functions/status_vps.sh
new file mode 100755
index 0000000..91996e9
--- /dev/null
+++ b/functions/status_vps.sh
@@ -0,0 +1,8 @@
+status_vps() {
+ LABEL="$1"
+ curl -s -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ https://api.linode.com/v4/linode/instances | \
+ jq -r --arg LABEL "$LABEL" '
+ .data[] | select(.label == $LABEL) |
+ "Label: \(.label)\nID: \(.id)\nRegion: \(.region)\nType: \(.type)\nStatus: \(.status)\nIP: \(.ipv4[0])\nCreated: \(.created)"'
+}
diff --git a/functions/usage.sh b/functions/usage.sh
new file mode 100755
index 0000000..25861b8
--- /dev/null
+++ b/functions/usage.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+function usage() {
+ echo "Usage: genesisctl [command]"
+ echo "Commands:"
+ echo " watch-abuse Start abuse monitoring via IPTables"
+}
+
+function watch_abuse() {
+ echo "[*] Launching abuse watch via screen..."
+ screen -dmS abusewatch /usr/local/bin/genesisctl-watch-abuse.sh
+ echo "[✓] Abuse watch running in detached screen session 'abusewatch'"
+}
+
+case "$1" in
+ watch-abuse)
+ watch_abuse
+ ;;
+ *)
+ usage
+ ;;
+esac
diff --git a/functions/verify_ptr.sh b/functions/verify_ptr.sh
new file mode 100755
index 0000000..8ce2f6c
--- /dev/null
+++ b/functions/verify_ptr.sh
@@ -0,0 +1,29 @@
+verify_ptr() {
+ LABEL="$1"
+ IP=$(curl -s -H "Authorization: Bearer $LINODE_API_TOKEN" https://api.linode.com/v4/linode/instances \
+ | jq -r --arg LABEL "$LABEL" '.data[] | select(.label == $LABEL) | .ipv4[0]')
+ LINODE_ID=$(curl -s -H "Authorization: Bearer $LINODE_API_TOKEN" https://api.linode.com/v4/linode/instances \
+ | jq -r --arg LABEL "$LABEL" '.data[] | select(.label == $LABEL) | .id')
+
+ if [[ -z "$IP" || -z "$LINODE_ID" ]]; then
+ echo "❌ Could not retrieve IP or Linode ID for label '$LABEL'"
+ return 1
+ fi
+
+ echo "Re-attempting rDNS update for $LABEL ($IP)..."
+ PTR_NAME="${LABEL}.doinkle.pro"
+ RDNS_PAYLOAD=$(cat <<EOF
+{
+ "rdns": "$PTR_NAME"
+}
+EOF
+)
+
+ RESPONSE=$(curl -s -w "\nHTTP Status: %{http_code}\n" -X PUT \
+ -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d "$RDNS_PAYLOAD" \
+ "https://api.linode.com/v4/linode/instances/$LINODE_ID/ips/$IP")
+
+ echo "$RESPONSE"
+}
diff --git a/genesis_squeaky.sh b/genesis_squeaky.sh
new file mode 100755
index 0000000..431227b
--- /dev/null
+++ b/genesis_squeaky.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+set -e
+
+# === CONFIG ===
+GEN_HOSTNAME="genesis-vps-$RANDOM"
+TG_API_URL="https://api.telegram.org/bot<OPTIONAL-BOT>/sendMessage"
+TG_CHAT_ID="<OPTIONAL-ID>"
+
+# === STEP 1: Obfuscate Traceroute (ICMP & UDP/TCP Ports) ===
+echo "[*] Obfuscating traceroute and TTL paths..."
+iptables -A OUTPUT -p icmp --icmp-type time-exceeded -j DROP
+iptables -A INPUT -p udp --dport 33434:33534 -j DROP
+iptables -A INPUT -p tcp --dport 33434:33534 -j DROP
+echo "[+] Firewall rules added."
+
+# === STEP 2: Set a Neutral Hostname ===
+echo "[*] Setting hostname to $GEN_HOSTNAME"
+hostnamectl set-hostname "$GEN_HOSTNAME"
+sed -i "s/^127.0.1.1.*/127.0.1.1 $GEN_HOSTNAME/" /etc/hosts
+echo "[+] Hostname set."
+
+# === STEP 3: Remove Linode Metadata Access ===
+echo "[*] Disabling Linode metadata agent (if present)..."
+systemctl stop linode-cloudinit 2>/dev/null || true
+systemctl disable linode-cloudinit 2>/dev/null || true
+touch /etc/cloud/cloud-init.disabled
+rm -rf /etc/cloud /var/lib/cloud /var/log/cloud-init.log
+echo "[+] Cloud-init neutered."
+
+# === STEP 4: Scrub Linode Stuff ===
+echo "[*] Scrubbing Linode fingerprints..."
+rm -f /etc/motd /etc/update-motd.d/linode
+rm -rf /usr/share/linode*
+rm -f /etc/apt/sources.list.d/linode.list
+apt remove --purge -y linode-cli linode-config 2>/dev/null || true
+yum remove -y linode-cli linode-config 2>/dev/null || true
+echo "[+] Linode packages and branding removed."
+
+# === STEP 5: Optional Telegram Notice ===
+# Uncomment if you want to alert yourself when a VPS is hardened
+# curl -s -X POST "$TG_API_URL" -d chat_id="$TG_CHAT_ID" -d text="Genesis VPS hardened: $GEN_HOSTNAME is stealth-ready." > /dev/null
+
+# === STEP 6: Final Touch ===
+echo "[✅] Genesis VPS hardened. You are now off-the-grid and good to go."
diff --git a/genesisctl.sh b/genesisctl.sh
new file mode 100755
index 0000000..21fdf7d
--- /dev/null
+++ b/genesisctl.sh
@@ -0,0 +1,104 @@
+#!/usr/bin/env bash
+# genesisctl - Genesis VPS Provisioning and Reboot CLI
+# Usage:
+# genesisctl provision <label> <region> <type> <image> [root_pass]
+# genesisctl reboot <linode-id>
+# genesisctl list regions|types|images
+# genesisctl ultra <label> [root_pass]
+# genesisctl safe <label> [root_pass]
+# genesisctl micro <label> [root_pass]
+# genesisctl mastodon <label> [root_pass]
+# genesisctl destroy <label>
+
+LINODE_API_TOKEN="f8b1552bf1f2f791e16fed0c1474d56014330de1c33810527523e44a7389cb6f"
+
+# Package presets
+PACKAGE_ULTRA_REGION="us-east"
+PACKAGE_ULTRA_TYPE="g6-dedicated-4"
+PACKAGE_ULTRA_IMAGE="linode/ubuntu22.04"
+
+PACKAGE_SAFE_REGION="us-east"
+PACKAGE_SAFE_TYPE="g6-standard-2"
+PACKAGE_SAFE_IMAGE="linode/ubuntu22.04"
+
+PACKAGE_MICRO_REGION="us-east"
+PACKAGE_MICRO_TYPE="g6-nanode-1"
+PACKAGE_MICRO_IMAGE="linode/ubuntu22.04"
+
+PACKAGE_MASTODON_REGION="us-east"
+PACKAGE_MASTODON_TYPE="g6-standard-4"
+PACKAGE_MASTODON_IMAGE="linode/ubuntu22.04"
+
+for f in functions/*.sh; do source "$f"; done
+
+# Helper for DNS pre-propagation check (used after provisioning)
+await_dns_propagation() {
+ HOSTNAME="$1"
+ EXPECTED_IP="$2"
+
+ echo "⏳ Waiting for DNS A record to propagate for $HOSTNAME to $EXPECTED_IP..."
+ for i in {1..10}; do
+ ACTUAL_IP=$(dig +short "$HOSTNAME")
+ if [[ "$ACTUAL_IP" == "$EXPECTED_IP" ]]; then
+ echo "✅ DNS A record found: $HOSTNAME → $EXPECTED_IP"
+ return 0
+ fi
+ echo "...still waiting ($i/10)..."
+ sleep 10
+ done
+ echo "❌ DNS A record for $HOSTNAME did not propagate in time. Skipping rDNS setup."
+ return 1
+}
+
+case "$1" in
+ provision)
+ provision_vps "$2" "$3" "$4" "$5" "$6"
+ ;;
+ reboot)
+ reboot_vps "$2"
+ ;;
+ destroy)
+ destroy_vps_by_label "$2"
+ ;;
+ safe)
+ provision_vps "$2" "$PACKAGE_SAFE_REGION" "$PACKAGE_SAFE_TYPE" "$PACKAGE_SAFE_IMAGE" "$3"
+ ;;
+ ultra)
+ provision_vps "$2" "$PACKAGE_ULTRA_REGION" "$PACKAGE_ULTRA_TYPE" "$PACKAGE_ULTRA_IMAGE" "$3"
+ ;;
+ micro)
+ provision_vps "$2" "$PACKAGE_MICRO_REGION" "$PACKAGE_MICRO_TYPE" "$PACKAGE_MICRO_IMAGE" "$3"
+ ;;
+ mastodon)
+ provision_vps "$2" "$PACKAGE_MASTODON_REGION" "$PACKAGE_MASTODON_TYPE" "$PACKAGE_MASTODON_IMAGE" "$3"
+ ;;
+ backup)
+ enable_backups_by_label "$2"
+ ;;
+ disable-backup)
+ disable_backups_by_label "$2"
+ ;;
+ status)
+ status_vps "$2"
+ ;;
+ listvps)
+ list_all_vps
+ ;;
+ disable)
+ disable_ip "$2"
+ ;;
+ resize)
+ resize_vps "$2" "$3"
+ ;;
+ safe-create)
+ safe_create_dataset "$2" "$3"
+ ;;
+ verify_ptr)
+ verify_ptr "$2"
+ ;;
+ *)
+ echo "Usage: $0 <command> [...]"
+ echo "Available commands: provision, reboot, destroy, safe, ultra, micro, mastodon"
+ exit 1
+ ;;
+esac
diff --git a/vps/.env b/vps/.env
new file mode 100644
index 0000000..1be49f0
--- /dev/null
+++ b/vps/.env
@@ -0,0 +1,5 @@
+LINODE_API_TOKEN=8140523e8d64f16f490b70096b04d221a44236eda552b0caa35fe9be35442f6d
+# Cloudflare API
+CF_API_TOKEN="PrUbZD1bj0ky1T32waiis2hp91e4Az1ZiCule9Ys"
+CF_ZONE_ID="c9b0c727c2c55594f62d38227133e3ac"
+CF_DOMAIN="failzero.net"
diff --git a/vps/check-hardened.sh b/vps/check-hardened.sh
new file mode 100755
index 0000000..cdaeef8
--- /dev/null
+++ b/vps/check-hardened.sh
@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+# check-hardened.sh - Scan all known Genesis VPSes for hardening status
+# Requirements: ssh access to all VPSes by label or IP
+
+LOG_BASE="/home/doc/vpslogs"
+MARKER_FILE="/var/log/genesis-hardened.ok"
+
+if [ ! -d "$LOG_BASE" ]; then
+ echo "❌ Log directory $LOG_BASE does not exist. Are you running this on Krang?"
+ exit 1
+fi
+
+cd "$LOG_BASE" || exit 1
+
+echo "🔍 Scanning for hardened Genesis VPSes..."
+echo
+
+for LOG in *.log; do
+ VPS_LABEL="${LOG%.log}"
+ LAST_KNOWN_IP=$(grep -Eo '\([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\)' "$LOG" | tail -1 | tr -d '()')
+
+ if [ -z "$LAST_KNOWN_IP" ]; then
+ echo "⚠️ $VPS_LABEL: No IP found in log. Skipping."
+ continue
+ fi
+
+ echo -n "🔧 $VPS_LABEL ($LAST_KNOWN_IP): "
+
+ ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no root@"$LAST_KNOWN_IP" "test -f $MARKER_FILE" >/dev/null 2>&1
+
+ if [ $? -eq 0 ]; then
+ echo "✅ Hardened"
+ else
+ echo "❌ Not marked as hardened"
+ fi
+
+done
diff --git a/vps/check_rdns_retry.sh b/vps/check_rdns_retry.sh
new file mode 100755
index 0000000..b11208b
--- /dev/null
+++ b/vps/check_rdns_retry.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+set -e
+[ -f ".env" ] && source .env
+LOGFILE="/home/doc/vpslogs/pending_rdns.log"
+TMPFILE="/tmp/rdns_retry.log"
+
+touch "$TMPFILE"
+
+while IFS="|" read -r LINODE_ID IP LABEL; do
+ CURRENT_RDNS=$(dig -x "$IP" +short)
+ EXPECTED_RDNS="$LABEL.failzero.net."
+
+ if [[ "$CURRENT_RDNS" == "$EXPECTED_RDNS" ]]; then
+ echo "✅ $IP already has correct rDNS ($CURRENT_RDNS)"
+ else
+ echo "⏳ rDNS not set correctly for $LABEL ($IP). Retrying..."
+ RESPONSE=$(curl -s -X PUT "https://api.linode.com/v4/linode/instances/$LINODE_ID/ips/$IP" \
+ -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"rdns": "'"$LABEL.failzero.net"'"}')
+ echo "🔁 Retry result for $IP: $RESPONSE"
+ fi
+
+ echo "$LINODE_ID|$IP|$LABEL" >> "$TMPFILE"
+done < "$LOGFILE"
+
+mv "$TMPFILE" "$LOGFILE"
diff --git a/vps/functions/destroy_vps_by_label.sh b/vps/functions/destroy_vps_by_label.sh
new file mode 100755
index 0000000..09d807e
--- /dev/null
+++ b/vps/functions/destroy_vps_by_label.sh
@@ -0,0 +1,28 @@
+destroy_vps_by_label() {
+ LABEL="$1"
+ echo "Looking for VPS with label '$LABEL'..."
+ LINODE_ID=$(curl -s -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ https://api.linode.com/v4/linode/instances | \
+ jq -r --arg LABEL "$LABEL" '.data[] | select(.label == $LABEL) | .id')
+
+ if [ -z "$LINODE_ID" ]; then
+ echo "Error: No Linode found with label '$LABEL'"
+ exit 1
+ fi
+
+ read -rp "Are you sure you want to destroy VPS '$LABEL' (ID: $LINODE_ID)? [y/N] " confirm
+ if [[ "$confirm" =~ ^[Yy]$ ]]; then
+ echo "Destroying Linode with ID $LINODE_ID (label: $LABEL)..."
+ HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
+ https://api.linode.com/v4/linode/instances/$LINODE_ID \
+ -H "Authorization: Bearer $LINODE_API_TOKEN")
+
+ if [[ "$HTTP_STATUS" == "204" ]]; then
+ echo "✅ Linode $LABEL (ID $LINODE_ID) has been destroyed."
+ else
+ echo "❌ Failed to destroy VPS. HTTP status: $HTTP_STATUS"
+ fi
+ else
+ echo "Cancelled. VPS '$LABEL' not destroyed."
+ fi
+}
diff --git a/vps/functions/disable_backups_by_label.sh b/vps/functions/disable_backups_by_label.sh
new file mode 100755
index 0000000..417bdb8
--- /dev/null
+++ b/vps/functions/disable_backups_by_label.sh
@@ -0,0 +1,23 @@
+disable_backups_by_label() {
+ LABEL="$1"
+ LINODE_ID=$(curl -s -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ https://api.linode.com/v4/linode/instances | \
+ jq -r --arg LABEL "$LABEL" '.data[] | select(.label == $LABEL) | .id')
+
+ if [ -z "$LINODE_ID" ]; then
+ echo "❌ No Linode found with label '$LABEL'"
+ exit 1
+ fi
+
+ echo "Disabling backups for Linode '$LABEL' (ID: $LINODE_ID)..."
+
+ HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
+ https://api.linode.com/v4/linode/instances/$LINODE_ID/backups/disable \
+ -H "Authorization: Bearer $LINODE_API_TOKEN")
+
+ if [[ "$HTTP_STATUS" == "200" ]]; then
+ echo "✅ Backups disabled for Linode $LABEL."
+ else
+ echo "❌ Failed to disable backups (HTTP $HTTP_STATUS)"
+ fi
+}
diff --git a/vps/functions/disable_ip.sh b/vps/functions/disable_ip.sh
new file mode 100644
index 0000000..0021b74
--- /dev/null
+++ b/vps/functions/disable_ip.sh
@@ -0,0 +1,18 @@
+disable_ip() {
+ local ip="$1"
+
+ if [[ -z "$ip" ]]; then
+ echo "[!] No IP specified."
+ exit 1
+ fi
+
+ echo "[*] Disabling access to VPS with IP: $ip"
+
+ # Block all traffic to/from that IP via iptables
+ iptables -A INPUT -s "$ip" -j DROP
+ iptables -A OUTPUT -d "$ip" -j DROP
+
+ echo "$ip - disabled on $(date)" >> /var/log/genesis-disabled.log
+
+ echo "[✓] $ip has been blocked and logged."
+}
diff --git a/vps/functions/enable_backups_by_label.sh b/vps/functions/enable_backups_by_label.sh
new file mode 100755
index 0000000..08fb31d
--- /dev/null
+++ b/vps/functions/enable_backups_by_label.sh
@@ -0,0 +1,23 @@
+enable_backups_by_label() {
+ LABEL="$1"
+ LINODE_ID=$(curl -s -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ https://api.linode.com/v4/linode/instances | \
+ jq -r --arg LABEL "$LABEL" '.data[] | select(.label == $LABEL) | .id')
+
+ if [ -z "$LINODE_ID" ]; then
+ echo "❌ No Linode found with label '$LABEL'"
+ exit 1
+ fi
+
+ echo "Enabling backups for Linode '$LABEL' (ID: $LINODE_ID)..."
+
+ HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
+ https://api.linode.com/v4/linode/instances/$LINODE_ID/backups/enable \
+ -H "Authorization: Bearer $LINODE_API_TOKEN")
+
+ if [[ "$HTTP_STATUS" == "200" ]]; then
+ echo "✅ Backups enabled for Linode $LABEL."
+ else
+ echo "❌ Failed to enable backups (HTTP $HTTP_STATUS)"
+ fi
+}
diff --git a/vps/functions/list_all_vps.sh b/vps/functions/list_all_vps.sh
new file mode 100755
index 0000000..8ce99eb
--- /dev/null
+++ b/vps/functions/list_all_vps.sh
@@ -0,0 +1,9 @@
+list_all_vps() {
+ curl -s -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ https://api.linode.com/v4/linode/instances | \
+ jq -r '
+ .data[] | [.label, .id, .region, .type, .ipv4[0], .status] |
+ @tsv' | column -t -s $'\t' | \
+ awk 'BEGIN { print "LABEL ID REGION TYPE IP STATUS" }
+ { printf "%-11s %-10s %-10s %-16s %-15s %s\n", $1, $2, $3, $4, $5, $6 }'
+}
diff --git a/vps/functions/provision.sh b/vps/functions/provision.sh
new file mode 100755
index 0000000..f6e9d39
--- /dev/null
+++ b/vps/functions/provision.sh
@@ -0,0 +1,135 @@
+provision_vps() {
+ LABEL="$1"
+ REGION="$2"
+ TYPE="$3"
+ IMAGE="$4"
+ ROOT_PASS="${5:-$(openssl rand -base64 16)}"
+
+ if [[ "$LINODE_API_TOKEN" == "REPLACE_WITH_YOUR_LINODE_API_TOKEN" ]]; then
+ echo "❌ Error: You must set your LINODE_API_TOKEN at the top of this script."
+ exit 1
+ fi
+
+ CLOUD_INIT=$(cat <<EOF
+#cloud-config
+hostname: genesis-vps
+manage_etc_hosts: true
+write_files:
+ - path: /usr/local/bin/genesis_squeaky.sh
+ permissions: '0755'
+ content: |
+ #!/bin/bash
+ set -e
+ GEN_HOSTNAME="genesis-vps-$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 6)"
+ LOGDIR="/home/doc/vpslogs"
+ LOGFILE="$LOGDIR/$GEN_HOSTNAME.log"
+ IP_ADDR=$(hostname -I | awk '{print $1}')
+
+ iptables -A OUTPUT -p icmp --icmp-type time-exceeded -j DROP
+ iptables -A INPUT -p udp --dport 33434:33534 -j DROP
+ iptables -A INPUT -p tcp --dport 33434:33534 -j DROP
+
+ hostnamectl set-hostname "$GEN_HOSTNAME"
+ sed -i "s/^127.0.1.1.*/127.0.1.1 $GEN_HOSTNAME/" /etc/hosts
+
+ systemctl stop linode-cloudinit 2>/dev/null || true
+ systemctl disable linode-cloudinit 2>/dev/null || true
+ touch /etc/cloud/cloud-init.disabled
+ rm -rf /etc/cloud /var/lib/cloud /var/log/cloud-init.log
+
+ rm -f /etc/motd /etc/update-motd.d/linode
+ rm -rf /usr/share/linode*
+ rm -f /etc/apt/sources.list.d/linode.list
+ apt remove --purge -y linode-cli linode-config 2>/dev/null || true
+
+ echo "[genesisctl] Attempting to log to Krang via webhook..." >> /var/log/genesis-harden.log
+ curl -s -X POST -H "Content-Type: application/json" \
+ -d "{\"host\": \"$GEN_HOSTNAME\", \"ip\": \"$IP_ADDR\", \"timestamp\": \"$(date)\"}" \
+ http://krang.core.sshjunkie.com:8080/genesislog >> /var/log/genesis-harden.log 2>&1 || echo "[genesisctl] Krang webhook logging failed" >> /var/log/genesis-harden.log
+
+ touch /var/log/genesis-hardened.ok
+
+runcmd:
+ - [ bash, /usr/local/bin/genesis_squeaky.sh ]
+EOF
+)
+
+ USER_DATA=$(echo "$CLOUD_INIT" | base64 -w 0)
+
+ echo "Provisioning VPS '$LABEL' in $REGION with type $TYPE and image $IMAGE..."
+ TMP_FILE=$(mktemp)
+ JSON_PAYLOAD=$(cat <<EOF
+{
+ "label": "$LABEL",
+ "region": "$REGION",
+ "type": "$TYPE",
+ "image": "$IMAGE",
+ "authorized_users": [],
+ "root_pass": "$ROOT_PASS",
+ "booted": true,
+ "metadata": {
+ "user_data": "$USER_DATA"
+ }
+}
+EOF
+)
+
+ HTTP_STATUS=$(curl -s -o "$TMP_FILE" -w "%{http_code}" -X POST https://api.linode.com/v4/linode/instances \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ -d "$JSON_PAYLOAD")
+
+ echo -e "\n--- HTTP STATUS: $HTTP_STATUS ---"
+ echo "--- RAW RESPONSE: ---"
+ cat "$TMP_FILE"
+
+ if [[ "$HTTP_STATUS" != "200" && "$HTTP_STATUS" != "201" ]]; then
+ echo -e "\n❌ Failed to provision VPS (HTTP $HTTP_STATUS)"
+ jq . "$TMP_FILE"
+ exit 1
+ fi
+
+ echo -e "\n✅ VPS provisioned:"
+ IP=$(jq -r '.ipv4[0]' "$TMP_FILE")
+ LINODE_ID=$(jq -r '.id' "$TMP_FILE")
+ echo "Label: $LABEL"
+ echo "IP Address: $IP"
+ echo "Root Password: $ROOT_PASS"
+
+ # Add DNS record to Cloudflare
+ echo "📡 Adding A record for $LABEL.$CF_DOMAIN → $IP..."
+ echo "[DEBUG] CF_API_TOKEN=$CF_API_TOKEN"
+ curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \
+ -H "Authorization: Bearer $CF_API_TOKEN" \
+ -H "Content-Type: application/json" \
+ --data-binary @<(cat <<JSON
+{
+ "type": "A",
+ "name": "$LABEL.$CF_DOMAIN",
+ "content": "$IP",
+ "ttl": 120,
+ "proxied": false
+}
+JSON
+) | jq '.success, .errors, .messages'
+
+ echo "⏳ Waiting indefinitely for DNS to propagate before setting rDNS..."
+i=1
+while true; do
+ CURRENT_IP=$(dig +short "$LABEL.$CF_DOMAIN")
+ if [[ "$CURRENT_IP" == "$IP" ]]; then
+ echo "✅ A record resolved. Setting rDNS..."
+ curl -s -X PUT "https://api.linode.com/v4/linode/instances/$LINODE_ID/ips/$IP" \
+ -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"rdns": "'"$LABEL.$CF_DOMAIN"'"}'
+ break
+ fi
+ echo "⏳ Attempt $i: DNS not ready. Waiting 15s..."
+ sleep 15
+ ((i++))
+done
+
+
+ echo "$LINODE_ID|$IP|$LABEL" >> /home/doc/vpslogs/pending_rdns.log
+}
diff --git a/vps/functions/reboot_vps.sh b/vps/functions/reboot_vps.sh
new file mode 100755
index 0000000..2741b9c
--- /dev/null
+++ b/vps/functions/reboot_vps.sh
@@ -0,0 +1,7 @@
+reboot_vps() {
+ LINODE_ID="$1"
+ echo "Rebooting Linode VPS ID $LINODE_ID..."
+
+ curl -s -X POST https://api.linode.com/v4/linode/instances/$LINODE_ID/reboot \
+ -H "Authorization: Bearer $LINODE_API_TOKEN" | jq
+}
diff --git a/vps/functions/resize_vps.sh b/vps/functions/resize_vps.sh
new file mode 100755
index 0000000..c06ea91
--- /dev/null
+++ b/vps/functions/resize_vps.sh
@@ -0,0 +1,27 @@
+resize_vps() {
+ LABEL="$1"
+ NEW_TYPE="$2"
+
+ LINODE_ID=$(curl -s -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ https://api.linode.com/v4/linode/instances | \
+ jq -r --arg LABEL "$LABEL" '.data[] | select(.label == $LABEL) | .id')
+
+ if [ -z "$LINODE_ID" ]; then
+ echo "❌ No Linode found with label '$LABEL'"
+ exit 1
+ fi
+
+ echo "Resizing Linode '$LABEL' to type '$NEW_TYPE'..."
+
+ HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ -d '{"type": "'"$NEW_TYPE"'"}' \
+ https://api.linode.com/v4/linode/instances/$LINODE_ID/resize)
+
+ if [[ "$HTTP_STATUS" == "200" ]]; then
+ echo "✅ Linode $LABEL resized to $NEW_TYPE."
+ else
+ echo "❌ Failed to resize VPS. HTTP status: $HTTP_STATUS"
+ fi
+}
diff --git a/vps/functions/safe_create_dataset.sh b/vps/functions/safe_create_dataset.sh
new file mode 100755
index 0000000..1960e55
--- /dev/null
+++ b/vps/functions/safe_create_dataset.sh
@@ -0,0 +1,12 @@
+safe_create_dataset() {
+ FULLPATH="$1"
+
+ # Remove any trailing slash
+ FULLPATH="${FULLPATH%/}"
+
+ POOL="${FULLPATH%%/*}"
+ DATASET="${FULLPATH#*/}"
+
+ echo "🛰 Connecting to Shredder to safely create '${POOL}/${DATASET}'..."
+ ssh shredder "/usr/local/bin/genesis-safe-zfs.sh $POOL $DATASET"
+}
diff --git a/vps/functions/status_vps.sh b/vps/functions/status_vps.sh
new file mode 100755
index 0000000..91996e9
--- /dev/null
+++ b/vps/functions/status_vps.sh
@@ -0,0 +1,8 @@
+status_vps() {
+ LABEL="$1"
+ curl -s -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ https://api.linode.com/v4/linode/instances | \
+ jq -r --arg LABEL "$LABEL" '
+ .data[] | select(.label == $LABEL) |
+ "Label: \(.label)\nID: \(.id)\nRegion: \(.region)\nType: \(.type)\nStatus: \(.status)\nIP: \(.ipv4[0])\nCreated: \(.created)"'
+}
diff --git a/vps/functions/usage.sh b/vps/functions/usage.sh
new file mode 100755
index 0000000..25861b8
--- /dev/null
+++ b/vps/functions/usage.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+function usage() {
+ echo "Usage: genesisctl [command]"
+ echo "Commands:"
+ echo " watch-abuse Start abuse monitoring via IPTables"
+}
+
+function watch_abuse() {
+ echo "[*] Launching abuse watch via screen..."
+ screen -dmS abusewatch /usr/local/bin/genesisctl-watch-abuse.sh
+ echo "[✓] Abuse watch running in detached screen session 'abusewatch'"
+}
+
+case "$1" in
+ watch-abuse)
+ watch_abuse
+ ;;
+ *)
+ usage
+ ;;
+esac
diff --git a/vps/functions/verify_ptr.sh b/vps/functions/verify_ptr.sh
new file mode 100755
index 0000000..8ce2f6c
--- /dev/null
+++ b/vps/functions/verify_ptr.sh
@@ -0,0 +1,29 @@
+verify_ptr() {
+ LABEL="$1"
+ IP=$(curl -s -H "Authorization: Bearer $LINODE_API_TOKEN" https://api.linode.com/v4/linode/instances \
+ | jq -r --arg LABEL "$LABEL" '.data[] | select(.label == $LABEL) | .ipv4[0]')
+ LINODE_ID=$(curl -s -H "Authorization: Bearer $LINODE_API_TOKEN" https://api.linode.com/v4/linode/instances \
+ | jq -r --arg LABEL "$LABEL" '.data[] | select(.label == $LABEL) | .id')
+
+ if [[ -z "$IP" || -z "$LINODE_ID" ]]; then
+ echo "❌ Could not retrieve IP or Linode ID for label '$LABEL'"
+ return 1
+ fi
+
+ echo "Re-attempting rDNS update for $LABEL ($IP)..."
+ PTR_NAME="${LABEL}.doinkle.pro"
+ RDNS_PAYLOAD=$(cat <<EOF
+{
+ "rdns": "$PTR_NAME"
+}
+EOF
+)
+
+ RESPONSE=$(curl -s -w "\nHTTP Status: %{http_code}\n" -X PUT \
+ -H "Authorization: Bearer $LINODE_API_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d "$RDNS_PAYLOAD" \
+ "https://api.linode.com/v4/linode/instances/$LINODE_ID/ips/$IP")
+
+ echo "$RESPONSE"
+}
diff --git a/vps/genesis_squeaky.sh b/vps/genesis_squeaky.sh
new file mode 100755
index 0000000..431227b
--- /dev/null
+++ b/vps/genesis_squeaky.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+set -e
+
+# === CONFIG ===
+GEN_HOSTNAME="genesis-vps-$RANDOM"
+TG_API_URL="https://api.telegram.org/bot<OPTIONAL-BOT>/sendMessage"
+TG_CHAT_ID="<OPTIONAL-ID>"
+
+# === STEP 1: Obfuscate Traceroute (ICMP & UDP/TCP Ports) ===
+echo "[*] Obfuscating traceroute and TTL paths..."
+iptables -A OUTPUT -p icmp --icmp-type time-exceeded -j DROP
+iptables -A INPUT -p udp --dport 33434:33534 -j DROP
+iptables -A INPUT -p tcp --dport 33434:33534 -j DROP
+echo "[+] Firewall rules added."
+
+# === STEP 2: Set a Neutral Hostname ===
+echo "[*] Setting hostname to $GEN_HOSTNAME"
+hostnamectl set-hostname "$GEN_HOSTNAME"
+sed -i "s/^127.0.1.1.*/127.0.1.1 $GEN_HOSTNAME/" /etc/hosts
+echo "[+] Hostname set."
+
+# === STEP 3: Remove Linode Metadata Access ===
+echo "[*] Disabling Linode metadata agent (if present)..."
+systemctl stop linode-cloudinit 2>/dev/null || true
+systemctl disable linode-cloudinit 2>/dev/null || true
+touch /etc/cloud/cloud-init.disabled
+rm -rf /etc/cloud /var/lib/cloud /var/log/cloud-init.log
+echo "[+] Cloud-init neutered."
+
+# === STEP 4: Scrub Linode Stuff ===
+echo "[*] Scrubbing Linode fingerprints..."
+rm -f /etc/motd /etc/update-motd.d/linode
+rm -rf /usr/share/linode*
+rm -f /etc/apt/sources.list.d/linode.list
+apt remove --purge -y linode-cli linode-config 2>/dev/null || true
+yum remove -y linode-cli linode-config 2>/dev/null || true
+echo "[+] Linode packages and branding removed."
+
+# === STEP 5: Optional Telegram Notice ===
+# Uncomment if you want to alert yourself when a VPS is hardened
+# curl -s -X POST "$TG_API_URL" -d chat_id="$TG_CHAT_ID" -d text="Genesis VPS hardened: $GEN_HOSTNAME is stealth-ready." > /dev/null
+
+# === STEP 6: Final Touch ===
+echo "[✅] Genesis VPS hardened. You are now off-the-grid and good to go."
diff --git a/vps/genesisctl.sh b/vps/genesisctl.sh
new file mode 100755
index 0000000..21fdf7d
--- /dev/null
+++ b/vps/genesisctl.sh
@@ -0,0 +1,104 @@
+#!/usr/bin/env bash
+# genesisctl - Genesis VPS Provisioning and Reboot CLI
+# Usage:
+# genesisctl provision <label> <region> <type> <image> [root_pass]
+# genesisctl reboot <linode-id>
+# genesisctl list regions|types|images
+# genesisctl ultra <label> [root_pass]
+# genesisctl safe <label> [root_pass]
+# genesisctl micro <label> [root_pass]
+# genesisctl mastodon <label> [root_pass]
+# genesisctl destroy <label>
+
+LINODE_API_TOKEN="f8b1552bf1f2f791e16fed0c1474d56014330de1c33810527523e44a7389cb6f"
+
+# Package presets
+PACKAGE_ULTRA_REGION="us-east"
+PACKAGE_ULTRA_TYPE="g6-dedicated-4"
+PACKAGE_ULTRA_IMAGE="linode/ubuntu22.04"
+
+PACKAGE_SAFE_REGION="us-east"
+PACKAGE_SAFE_TYPE="g6-standard-2"
+PACKAGE_SAFE_IMAGE="linode/ubuntu22.04"
+
+PACKAGE_MICRO_REGION="us-east"
+PACKAGE_MICRO_TYPE="g6-nanode-1"
+PACKAGE_MICRO_IMAGE="linode/ubuntu22.04"
+
+PACKAGE_MASTODON_REGION="us-east"
+PACKAGE_MASTODON_TYPE="g6-standard-4"
+PACKAGE_MASTODON_IMAGE="linode/ubuntu22.04"
+
+for f in functions/*.sh; do source "$f"; done
+
+# Helper for DNS pre-propagation check (used after provisioning)
+await_dns_propagation() {
+ HOSTNAME="$1"
+ EXPECTED_IP="$2"
+
+ echo "⏳ Waiting for DNS A record to propagate for $HOSTNAME to $EXPECTED_IP..."
+ for i in {1..10}; do
+ ACTUAL_IP=$(dig +short "$HOSTNAME")
+ if [[ "$ACTUAL_IP" == "$EXPECTED_IP" ]]; then
+ echo "✅ DNS A record found: $HOSTNAME → $EXPECTED_IP"
+ return 0
+ fi
+ echo "...still waiting ($i/10)..."
+ sleep 10
+ done
+ echo "❌ DNS A record for $HOSTNAME did not propagate in time. Skipping rDNS setup."
+ return 1
+}
+
+case "$1" in
+ provision)
+ provision_vps "$2" "$3" "$4" "$5" "$6"
+ ;;
+ reboot)
+ reboot_vps "$2"
+ ;;
+ destroy)
+ destroy_vps_by_label "$2"
+ ;;
+ safe)
+ provision_vps "$2" "$PACKAGE_SAFE_REGION" "$PACKAGE_SAFE_TYPE" "$PACKAGE_SAFE_IMAGE" "$3"
+ ;;
+ ultra)
+ provision_vps "$2" "$PACKAGE_ULTRA_REGION" "$PACKAGE_ULTRA_TYPE" "$PACKAGE_ULTRA_IMAGE" "$3"
+ ;;
+ micro)
+ provision_vps "$2" "$PACKAGE_MICRO_REGION" "$PACKAGE_MICRO_TYPE" "$PACKAGE_MICRO_IMAGE" "$3"
+ ;;
+ mastodon)
+ provision_vps "$2" "$PACKAGE_MASTODON_REGION" "$PACKAGE_MASTODON_TYPE" "$PACKAGE_MASTODON_IMAGE" "$3"
+ ;;
+ backup)
+ enable_backups_by_label "$2"
+ ;;
+ disable-backup)
+ disable_backups_by_label "$2"
+ ;;
+ status)
+ status_vps "$2"
+ ;;
+ listvps)
+ list_all_vps
+ ;;
+ disable)
+ disable_ip "$2"
+ ;;
+ resize)
+ resize_vps "$2" "$3"
+ ;;
+ safe-create)
+ safe_create_dataset "$2" "$3"
+ ;;
+ verify_ptr)
+ verify_ptr "$2"
+ ;;
+ *)
+ echo "Usage: $0 <command> [...]"
+ echo "Available commands: provision, reboot, destroy, safe, ultra, micro, mastodon"
+ exit 1
+ ;;
+esac