From 12e8546023f98163de2028651a90a5b78eab2de3 Mon Sep 17 00:00:00 2001 From: Dominik Roth Date: Sun, 24 Aug 2025 17:51:01 +0200 Subject: [PATCH] Add nullpoint cluster feature with WireGuard mesh and GlusterFS - New cluster-setup.sh script for creating/joining distributed storage clusters - Interactive menu: create new cluster or join existing - WireGuard mesh networking with automatic IP allocation - GlusterFS with full replication across all nodes - Single-node start capability, scales up as nodes join - Storage mounted at /data/storage/ with automatic firewall config - Simple wget installer for post-nullpoint-install usage --- README.md | 13 ++ cluster-setup.sh | 320 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 333 insertions(+) create mode 100755 cluster-setup.sh diff --git a/README.md b/README.md index c18c9f1..c3be6e6 100644 --- a/README.md +++ b/README.md @@ -42,3 +42,16 @@ The installer will: - Generate a secure LUKS passphrase (SAVE IT!) - Download and configure everything - Run Hetzner's installimage automatically + +## Nullpoint Cluster + +Create or join a distributed storage cluster with WireGuard mesh networking and GlusterFS. Start with a single node and scale up by adding more servers. + +```bash +wget -qO- https://git.dominik-roth.eu/dodox/nullpoint/raw/branch/master/cluster-setup.sh | sudo bash +``` + +- **Storage mounted at**: `/data/storage/` +- **All data replicated** to all cluster nodes +- **Secure WireGuard mesh** between nodes +- **Interactive setup** - choose create or join cluster diff --git a/cluster-setup.sh b/cluster-setup.sh new file mode 100755 index 0000000..6e47498 --- /dev/null +++ b/cluster-setup.sh @@ -0,0 +1,320 @@ +#!/bin/bash +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Configuration +WG_INTERFACE="wg-cluster" +WG_PORT=51820 +WG_NETWORK="10.10.0.0/24" +GLUSTER_BRICK_PATH="/gluster/cluster" +GLUSTER_MOUNT_PATH="/data/storage" +GLUSTER_VOLUME="cluster-volume" + +echo -e "${GREEN}================================${NC}" +echo -e "${GREEN} Nullpoint Cluster Setup${NC}" +echo -e "${GREEN}================================${NC}\n" + +# Check if running as root +if [ "$EUID" -ne 0 ]; then + echo -e "${RED}Please run as root${NC}" + exit 1 +fi + +# Install required packages +echo -e "${YELLOW}[+] Installing required packages...${NC}" +dnf install -y wireguard-tools glusterfs-server glusterfs-client || exit 1 + +# Enable and start GlusterFS +systemctl enable glusterd +systemctl start glusterd + +# Create directories +echo -e "${YELLOW}[+] Creating directories...${NC}" +mkdir -p "$GLUSTER_BRICK_PATH" +mkdir -p "$GLUSTER_MOUNT_PATH" +mkdir -p /data + +# Function to generate WireGuard keys +generate_wg_keys() { + local private_key=$(wg genkey) + local public_key=$(echo "$private_key" | wg pubkey) + local preshared_key=$(wg genpsk) + echo "$private_key:$public_key:$preshared_key" +} + +# Function to get next available IP +get_next_ip() { + local base_ip="10.10.0" + local next_num=2 + + if [ -f /etc/wireguard/${WG_INTERFACE}.conf ]; then + # Find highest IP in use + existing_ips=$(grep -E "AllowedIPs|Address" /etc/wireguard/${WG_INTERFACE}.conf | grep -oE "10\.10\.0\.[0-9]+" | cut -d. -f4 | sort -n | tail -1) + if [ ! -z "$existing_ips" ]; then + next_num=$((existing_ips + 1)) + fi + fi + + echo "${base_ip}.${next_num}" +} + +# Function to setup firewall rules +setup_firewall() { + echo -e "${YELLOW}[+] Configuring firewall...${NC}" + + # WireGuard + firewall-cmd --permanent --add-port=${WG_PORT}/udp + + # GlusterFS ports + firewall-cmd --permanent --add-service=glusterfs + firewall-cmd --permanent --add-port=24007-24008/tcp # GlusterFS Daemon + firewall-cmd --permanent --add-port=49152-49200/tcp # Brick ports + + # Allow traffic from WireGuard network + firewall-cmd --permanent --zone=trusted --add-source=${WG_NETWORK} + + firewall-cmd --reload +} + +# Create new cluster +create_cluster() { + echo -e "${GREEN}[*] Creating new cluster...${NC}\n" + + # Generate keys + keys=$(generate_wg_keys) + private_key=$(echo "$keys" | cut -d: -f1) + public_key=$(echo "$keys" | cut -d: -f2) + preshared_key=$(echo "$keys" | cut -d: -f3) + + # Create WireGuard config + cat > /etc/wireguard/${WG_INTERFACE}.conf </dev/null || true + gluster volume start ${GLUSTER_VOLUME} 2>/dev/null || true + + # Mount the volume locally + mount -t glusterfs localhost:/${GLUSTER_VOLUME} ${GLUSTER_MOUNT_PATH} + + # Add to fstab for persistence + grep -q "${GLUSTER_VOLUME}" /etc/fstab || echo "localhost:/${GLUSTER_VOLUME} ${GLUSTER_MOUNT_PATH} glusterfs defaults,_netdev 0 0" >> /etc/fstab + + # Get external IP for other nodes to connect + external_ip=$(ip route get 1.1.1.1 | awk '{print $7; exit}') + + echo -e "\n${GREEN}════════════════════════════════════════${NC}" + echo -e "${GREEN}Cluster created successfully!${NC}" + echo -e "${GREEN}════════════════════════════════════════${NC}\n" + echo -e "Share these details with nodes joining the cluster:\n" + echo -e "${YELLOW}Cluster IP:${NC} ${external_ip}" + echo -e "${YELLOW}WireGuard Port:${NC} ${WG_PORT}" + echo -e "${YELLOW}Public Key:${NC} ${public_key}" + echo -e "${YELLOW}Preshared Key:${NC} ${preshared_key}" + echo -e "\n${YELLOW}Cluster Secret (for easy sharing):${NC}" + echo -e "${GREEN}${external_ip}:${WG_PORT}:${public_key}:${preshared_key}${NC}" + echo -e "\n${YELLOW}Status:${NC}" + echo " - WireGuard interface: ${WG_INTERFACE} (10.10.0.1)" + echo " - GlusterFS volume: ${GLUSTER_VOLUME}" + echo " - Mount point: ${GLUSTER_MOUNT_PATH}" +} + +# Join existing cluster +join_cluster() { + echo -e "${GREEN}[*] Joining existing cluster...${NC}\n" + + # Get cluster details + read -p "Enter cluster node IP: " cluster_ip + read -p "Enter cluster secret (or press enter to input separately): " cluster_secret + + if [ -z "$cluster_secret" ]; then + read -p "Enter WireGuard port [${WG_PORT}]: " wg_port_input + wg_port=${wg_port_input:-$WG_PORT} + read -p "Enter cluster public key: " cluster_public_key + read -p "Enter preshared key: " preshared_key + else + # Parse secret + cluster_ip=$(echo "$cluster_secret" | cut -d: -f1) + wg_port=$(echo "$cluster_secret" | cut -d: -f2) + cluster_public_key=$(echo "$cluster_secret" | cut -d: -f3) + preshared_key=$(echo "$cluster_secret" | cut -d: -f4) + fi + + # Generate local keys + keys=$(generate_wg_keys) + private_key=$(echo "$keys" | cut -d: -f1) + public_key=$(echo "$keys" | cut -d: -f2) + + # Get next available IP + my_ip=$(get_next_ip) + + echo -e "${YELLOW}[+] Configuring WireGuard (IP: ${my_ip})...${NC}" + + # Create WireGuard config + cat > /etc/wireguard/${WG_INTERFACE}.conf < /dev/null 2>&1; then + echo -e "${RED}Failed to connect to cluster via WireGuard!${NC}" + echo "Please check the cluster details and firewall settings." + exit 1 + fi + + echo -e "${GREEN}[✓] WireGuard connection established${NC}" + + # Add this node to the cluster node's WireGuard config + echo -e "${YELLOW}[+] Requesting cluster to add this node as peer...${NC}" + + # SSH to cluster node and add peer (requires SSH key setup) + ssh_cmd="wg set ${WG_INTERFACE} peer ${public_key} preshared-key <(echo ${preshared_key}) allowed-ips ${my_ip}/32 persistent-keepalive 25" + + echo -e "${YELLOW}Run this command on the cluster node (10.10.0.1) to add this peer:${NC}" + echo -e "${GREEN}sudo wg set ${WG_INTERFACE} peer ${public_key} preshared-key <(echo ${preshared_key}) allowed-ips ${my_ip}/32 persistent-keepalive 25${NC}" + echo -e "${GREEN}sudo bash -c 'echo \"[Peer]\" >> /etc/wireguard/${WG_INTERFACE}.conf'${NC}" + echo -e "${GREEN}sudo bash -c 'echo \"PublicKey = ${public_key}\" >> /etc/wireguard/${WG_INTERFACE}.conf'${NC}" + echo -e "${GREEN}sudo bash -c 'echo \"PresharedKey = ${preshared_key}\" >> /etc/wireguard/${WG_INTERFACE}.conf'${NC}" + echo -e "${GREEN}sudo bash -c 'echo \"AllowedIPs = ${my_ip}/32\" >> /etc/wireguard/${WG_INTERFACE}.conf'${NC}" + echo -e "${GREEN}sudo bash -c 'echo \"PersistentKeepalive = 25\" >> /etc/wireguard/${WG_INTERFACE}.conf'${NC}" + + read -p "Press enter once you've added this peer to the cluster node..." + + # Join GlusterFS cluster + echo -e "${YELLOW}[+] Joining GlusterFS cluster...${NC}" + + # Probe the cluster + gluster peer probe 10.10.0.1 + + # Wait for peer to be connected + sleep 3 + + # Create brick directory + mkdir -p "${GLUSTER_BRICK_PATH}/brick1" + + # Add brick to existing volume and increase replica count + echo -e "${YELLOW}[+] Adding brick to GlusterFS volume...${NC}" + + # Get current replica count + replica_count=$(gluster volume info ${GLUSTER_VOLUME} 2>/dev/null | grep "Number of Bricks" | grep -oE "[0-9]+" | head -1) + new_replica_count=$((replica_count + 1)) + + # Add brick with increased replica count + gluster volume add-brick ${GLUSTER_VOLUME} replica ${new_replica_count} $(hostname):${GLUSTER_BRICK_PATH}/brick1 force + + # Mount the volume + mount -t glusterfs 10.10.0.1:/${GLUSTER_VOLUME} ${GLUSTER_MOUNT_PATH} + + # Add to fstab + grep -q "${GLUSTER_VOLUME}" /etc/fstab || echo "10.10.0.1:/${GLUSTER_VOLUME} ${GLUSTER_MOUNT_PATH} glusterfs defaults,_netdev 0 0" >> /etc/fstab + + echo -e "\n${GREEN}════════════════════════════════════════${NC}" + echo -e "${GREEN}Successfully joined cluster!${NC}" + echo -e "${GREEN}════════════════════════════════════════${NC}\n" + echo -e "${YELLOW}Node details:${NC}" + echo " - WireGuard IP: ${my_ip}" + echo " - Public Key: ${public_key}" + echo " - GlusterFS mounted at: ${GLUSTER_MOUNT_PATH}" +} + +# Show cluster status +show_status() { + echo -e "\n${YELLOW}=== Cluster Status ===${NC}\n" + + if [ -f /etc/wireguard/${WG_INTERFACE}.conf ]; then + echo -e "${GREEN}WireGuard Status:${NC}" + wg show ${WG_INTERFACE} + echo "" + else + echo -e "${RED}WireGuard not configured${NC}\n" + fi + + echo -e "${GREEN}GlusterFS Status:${NC}" + gluster peer status + echo "" + gluster volume status ${GLUSTER_VOLUME} 2>/dev/null || echo "Volume ${GLUSTER_VOLUME} not found" + echo "" + + echo -e "${GREEN}Mounted at:${NC} ${GLUSTER_MOUNT_PATH}" + df -h ${GLUSTER_MOUNT_PATH} 2>/dev/null || echo "Not mounted" +} + +# Main menu +echo "What would you like to do?" +echo " 1) Create new cluster" +echo " 2) Join existing cluster" +echo " 3) Show cluster status" +echo " 4) Exit" +echo "" +read -p "Enter choice [1-4]: " choice + +case $choice in + 1) + create_cluster + ;; + 2) + join_cluster + ;; + 3) + show_status + ;; + 4) + echo "Exiting..." + exit 0 + ;; + *) + echo -e "${RED}Invalid choice${NC}" + exit 1 + ;; +esac \ No newline at end of file