diff --git a/docker-deployments/nginxproxy.yml b/docker-deployments/nginxproxy.yml new file mode 100644 index 0000000..c80ba11 --- /dev/null +++ b/docker-deployments/nginxproxy.yml @@ -0,0 +1,35 @@ +services: + app: + image: 'jc21/nginx-proxy-manager:latest' + restart: unless-stopped + ports: + # These ports are in format : + - '80:80' # Public HTTP Port + - '443:443' # Public HTTPS Port + - '81:81' # Admin Web Port + # Add any other Stream port you want to expose + # - '21:21' # FTP + + environment: + - TZ=America/New_York + - PUID=1000 + - PGID=1000 + + + volumes: + - nginx-data:/data + - nginx-letsencrypt:/etc/letsencrypt + +volumes: + nginx-data: + driver: local + driver_opts: + type: nfs + o: "addr=14.10.10.71,rw,nfsvers=4" + device: ":/volume1/docker/nginx/data/" + nginx-letsencrypt: + driver: local + driver_opts: + type: nfs + o: "addr=14.10.10.71,rw,nfsvers=4" + device: ":/volume1/docker/nginx/letsencrypt/" diff --git a/docker-deployments/vscoder.yml b/docker-deployments/vscoder.yml new file mode 100644 index 0000000..490cb45 --- /dev/null +++ b/docker-deployments/vscoder.yml @@ -0,0 +1,47 @@ +version: "3.9" +services: + coder: + # This MUST be stable for our documentation and + # other automations. + image: ghcr.io/coder/coder:${CODER_VERSION:-latest} + ports: + - "7080:7080" + environment: + CODER_PG_CONNECTION_URL: "postgresql://${POSTGRES_USER:-username}:${POSTGRES_PASSWORD:-password}@database/${POSTGRES_DB:-coder}?sslmode=disable" + CODER_HTTP_ADDRESS: "0.0.0.0:7080" + # You'll need to set CODER_ACCESS_URL to an IP or domain + # that workspaces can reach. This cannot be localhost + # or 127.0.0.1 for non-Docker templates! + CODER_ACCESS_URL: "${CODER_ACCESS_URL}" + # If the coder user does not have write permissions on + # the docker socket, you can uncomment the following + # lines and set the group ID to one that has write + # permissions on the docker socket. + #group_add: "27" + #- "998" # docker group on host + volumes: + - /var/run/docker.sock:/var/run/docker.sock + # depends_on: + # database: + # condition: service_healthy + database: + image: "postgres:14.2" + ports: + - "5432:5432" + environment: + POSTGRES_USER: ${POSTGRES_USER:-username} # The PostgreSQL user (useful to connect to the database) + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password} # The PostgreSQL password (useful to connect to the database) + POSTGRES_DB: ${POSTGRES_DB:-coder} # The PostgreSQL default database (automatically created at first launch) + volumes: + - coder_data:/var/lib/postgresql/data # Use "docker volume rm coder_coder_data" to reset Coder + healthcheck: + test: + [ + "CMD-SHELL", + "pg_isready -U ${POSTGRES_USER:-username} -d ${POSTGRES_DB:-coder}", + ] + interval: 5s + timeout: 5s + retries: 5 +volumes: + coder_data: \ No newline at end of file diff --git a/scripts/deploy_portainer_agent.sh b/scripts/deploy_portainer_agent.sh new file mode 100644 index 0000000..8c62339 --- /dev/null +++ b/scripts/deploy_portainer_agent.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# +# Portainer Agent Deployment Script for Docker Swarm +# Usage: bash deploy_portainer_agent.sh +# + +set -euo pipefail + +log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } + +log "Checking Docker Swarm status..." +if ! docker info --format '{{.Swarm.LocalNodeState}}' | grep -q "active"; then + log "ERROR: This node is not part of a Docker Swarm." + exit 1 +fi + +if ! docker info --format '{{.Swarm.ControlAvailable}}' | grep -q "true"; then + log "ERROR: This script must be run on a Swarm MANAGER node." + exit 1 +fi + +log "Creating overlay network for Portainer Agent..." +if ! docker network ls | grep -q "portainer_agent_network"; then + docker network create --driver overlay portainer_agent_network +else + log "Network 'portainer_agent_network' already exists, skipping." +fi + +log "Deploying Portainer Agent as a global Swarm service..." +if docker service ls | grep -q "portainer_agent"; then + log "Agent service already exists. Updating to latest image..." + docker service update --image portainer/agent:latest portainer_agent +else + docker service create \ + --name portainer_agent \ + --network portainer_agent_network \ + --publish mode=host,target=9001,published=9001 \ + -e AGENT_CLUSTER_ADDR=tasks.portainer_agent \ + --mode global \ + --mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock \ + --mount type=bind,src=//var/lib/docker/volumes,dst=/var/lib/docker/volumes \ + --mount type=bind,src=//,dst=/host \ + portainer/agent:latest +fi + +log "=== Portainer Agent Deployment Complete ===" +log "The agent is now deploying to all nodes in your Swarm." +log "To manage your Swarm:" +log "1. Open your Portainer Server UI." +log "2. Go to 'Environments' -> 'Add environment'." +log "3. Select 'Docker Swarm' and click 'Start Wizard'." +log "4. Choose 'Agent' and enter the Environment address: $(hostname -I | awk '{print $1}'):9001" +log "5. Click 'Connect'." \ No newline at end of file diff --git a/scripts/export_portainer_stacks.sh b/scripts/export_portainer_stacks.sh new file mode 100644 index 0000000..b0d16d2 --- /dev/null +++ b/scripts/export_portainer_stacks.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# +# Portainer Stack Exporter +# Connects to a Portainer instance via API and downloads all Stack compose files. +# + +set -euo pipefail + +# Portainer configuration (Legacy Cluster) +PORTAINER_URL="https://shipyard.snarfnet.net:9443" +USERNAME="admin" +PASSWORD="Cfxg0GqDfocWcteE" +EXPORT_DIR="./portainer_stacks_export" + +if ! command -v jq &> /dev/null; then + echo "ERROR: 'jq' is required to parse the JSON responses. Please install it." + exit 1 +fi + +mkdir -p "$EXPORT_DIR" + +echo "=== Portainer Stack Exporter ===" +echo "Authenticating with Portainer at $PORTAINER_URL..." + +# 1. Authenticate and get JWT Token +JWT=$(curl -s -k -X POST "${PORTAINER_URL}/api/auth" \ + -H "Content-Type: application/json" \ + -d "{\"Username\":\"${USERNAME}\",\"Password\":\"${PASSWORD}\"}" | jq -r '.jwt') + +if [ "$JWT" = "null" ] || [ -z "$JWT" ]; then + echo "Authentication failed! Check your credentials or URL." + exit 1 +fi +echo "Authentication successful." + +# 2. Fetch Stacks list +echo "Fetching stacks..." +STACKS_JSON=$(curl -s -k -H "Authorization: Bearer $JWT" "${PORTAINER_URL}/api/stacks") + +# 3. Iterate through stacks, grab the file, and save it +echo "$STACKS_JSON" | jq -c '.[]' | while read -r stack; do + STACK_ID=$(echo "$stack" | jq -r '.Id') + STACK_NAME=$(echo "$stack" | jq -r '.Name') + + echo " -> Exporting stack: $STACK_NAME (ID: $STACK_ID)" + FILE_CONTENT=$(curl -s -k -H "Authorization: Bearer $JWT" "${PORTAINER_URL}/api/stacks/${STACK_ID}/file" | jq -r '.StackFileContent') + + echo "$FILE_CONTENT" > "${EXPORT_DIR}/${STACK_NAME}.yml" +done + +echo "=== Export Complete ===" +echo "Your stack files have been saved to: ${EXPORT_DIR}/" \ No newline at end of file diff --git a/scripts/install_docker_rpi.sh b/scripts/install_docker_rpi.sh new file mode 100644 index 0000000..402485c --- /dev/null +++ b/scripts/install_docker_rpi.sh @@ -0,0 +1,86 @@ +#!/bin/bash +# +# Docker Installation Script for Raspberry Pi OS 64-bit Lite +# Usage: sudo bash install_docker_rpi.sh +# + +set -euo pipefail + +log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } + +if [ "$(id -u)" -ne 0 ]; then + log "ERROR: This script must be run as root (use sudo)." + exit 1 +fi + +SWARM_INIT=false +SWARM_JOIN=false +SWARM_MANAGER_IP="" +SWARM_TOKEN="" + +echo "=== Docker Swarm Configuration ===" +read -r -p "Is this the first node of a new Docker Swarm? (y/N): " init_choice +if [[ "$init_choice" =~ ^[Yy]$ ]]; then + SWARM_INIT=true +else + read -r -p "Do you want to join an existing Docker Swarm? (y/N): " join_choice + if [[ "$join_choice" =~ ^[Yy]$ ]]; then + SWARM_JOIN=true + read -r -p "Enter Swarm Manager IP address: " SWARM_MANAGER_IP + read -r -p "Enter Swarm Join Token: " SWARM_TOKEN + fi +fi + +log "Updating system packages..." +apt-get update +apt-get upgrade -y + +log "Downloading and running Docker's official convenience script..." +curl -fsSL https://get.docker.com -o /tmp/get-docker.sh +sh /tmp/get-docker.sh + +log "Configuring user permissions..." +# Use the user who invoked sudo, otherwise default to 'pi' +TARGET_USER="${SUDO_USER:-pi}" + +if id "$TARGET_USER" &>/dev/null; then + usermod -aG docker "$TARGET_USER" + log "Added user '$TARGET_USER' to the 'docker' group." +else + log "WARNING: User '$TARGET_USER' not found, skipping group assignment." +fi + +log "Enabling Docker service to start on boot..." +systemctl enable docker +systemctl start docker + +# Grab the primary local IP address to advertise for Swarm +LOCAL_IP=$(hostname -I | awk '{print $1}') + +if [ "$SWARM_INIT" = true ]; then + log "Initializing Docker Swarm..." + docker swarm init --advertise-addr "$LOCAL_IP" + WORKER_TOKEN=$(docker swarm join-token worker -q) +elif [ "$SWARM_JOIN" = true ]; then + log "Joining Docker Swarm at $SWARM_MANAGER_IP..." + # Append default swarm port if the user didn't specify one + if [[ "$SWARM_MANAGER_IP" != *":"* ]]; then + SWARM_MANAGER_IP="${SWARM_MANAGER_IP}:2377" + fi + docker swarm join --advertise-addr "$LOCAL_IP" --token "$SWARM_TOKEN" "$SWARM_MANAGER_IP" +fi + +log "Cleaning up..." +rm -f /tmp/get-docker.sh + +log "=== Installation Complete ===" +log "Note: You must log out and log back in for the group changes to take effect," +log " or run 'newgrp docker' to use Docker as a non-root user immediately." + +if [ "$SWARM_INIT" = true ]; then + echo "" + log "=== Swarm Initialized ===" + log "To add worker nodes to this swarm, use the following as input variables:" + log "Manager IP: $LOCAL_IP" + log "Join Token: $WORKER_TOKEN" +fi \ No newline at end of file diff --git a/scripts/install_portainer.sh b/scripts/install_portainer.sh new file mode 100644 index 0000000..7dac6d2 --- /dev/null +++ b/scripts/install_portainer.sh @@ -0,0 +1,92 @@ +#!/bin/bash +# +# Portainer Installation Script +# Usage: bash install_portainer.sh +# + +set -euo pipefail + +echo "=== Portainer Configuration ===" +read -r -s -p "Enter desired Portainer admin password (min 12 chars): " PORTAINER_PASSWORD +echo "" +read -r -p "Enter your Portainer EE license key: " PORTAINER_LICENSE +echo "" + +log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } + +log "Starting Portainer deployment..." + +# Verify Docker is installed +if ! command -v docker &> /dev/null; then + log "ERROR: Docker is not installed or not in the PATH." + log "Please install Docker first." + exit 1 +fi + +# Verify Docker permissions (must be root or in the docker group) +if [ "$(id -u)" -ne 0 ] && ! groups | grep -q "\bdocker\b"; then + log "ERROR: Your user is not in the 'docker' group and you are not root." + log "Please run 'sudo usermod -aG docker \$USER', log out and back in, or run this script with sudo." + exit 1 +fi + +log "Creating secure admin password file..." +# Portainer requires a minimum 12-character password +if [ ${#PORTAINER_PASSWORD} -lt 12 ]; then + log "WARNING: Password is less than 12 characters. Portainer may reject it." +fi + +mkdir -p /opt/portainer +echo -n "$PORTAINER_PASSWORD" > /opt/portainer/admin_password +chmod 600 /opt/portainer/admin_password + +# Ensure password file is removed when the script exits +trap 'rm -f /opt/portainer/admin_password' EXIT + +log "Creating Portainer data volume (if it doesn't already exist)..." +docker volume create portainer_data + +log "Deploying Portainer container..." +docker run -d -p 8000:8000 -p 9443:9443 --name portainer \ + --restart=always \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v portainer_data:/data \ + -v /opt/portainer/admin_password:/tmp/admin_password \ + portainer/portainer-ee:latest \ + --admin-password-file '/tmp/admin_password' + +if [ -n "$PORTAINER_LICENSE" ] && [ -n "$PORTAINER_PASSWORD" ]; then + log "Waiting for Portainer to start up to apply license key via API..." + for i in {1..15}; do + if curl -ks -o /dev/null https://localhost:9443/; then + break + fi + sleep 2 + done + sleep 2 # Extra padding for API initialization + + log "Authenticating with Portainer API..." + JWT=$(curl -ks -X POST https://localhost:9443/api/auth \ + -H "Content-Type: application/json" \ + -d "{\"Username\":\"admin\",\"Password\":\"$PORTAINER_PASSWORD\"}" | grep -o '"jwt":"[^"]*"' | cut -d'"' -f4) + + if [ -n "$JWT" ]; then + log "Applying license key..." + LICENSE_STATUS=$(curl -ks -w "%{http_code}" -o /dev/null -X POST https://localhost:9443/api/licenses \ + -H "Authorization: Bearer $JWT" \ + -H "Content-Type: application/json" \ + -d "{\"licenseKey\":\"$PORTAINER_LICENSE\"}") + + if [ "$LICENSE_STATUS" = "200" ]; then + log "License key applied successfully!" + else + log "WARNING: Failed to apply license key (HTTP $LICENSE_STATUS). You may need to enter it manually." + fi + else + log "WARNING: Failed to authenticate with Portainer API. Please apply the license manually." + fi +fi + +log "=== Portainer Installation Complete ===" +log "You can now access the Portainer web interface at:" +log "https://:9443" \ No newline at end of file diff --git a/scripts/set_static_ip_nmcli.sh b/scripts/set_static_ip_nmcli.sh new file mode 100644 index 0000000..7066863 --- /dev/null +++ b/scripts/set_static_ip_nmcli.sh @@ -0,0 +1,103 @@ +#!/bin/bash +# +# Interactive Static IP Configuration Script using nmcli +# Usage: sudo bash set_static_ip_nmcli.sh +# + +set -euo pipefail + +log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } + +if [ "$(id -u)" -ne 0 ]; then + log "ERROR: This script must be run as root (use sudo)." + exit 1 +fi + +if ! command -v nmcli &> /dev/null; then + log "ERROR: nmcli is not installed. This system may not be using NetworkManager." + exit 1 +fi + +echo "=========================================" +echo " NetworkManager Static IP Setup" +echo "=========================================" +echo "" +log "Available Network Connections:" +mapfile -t CONNECTIONS < <(nmcli -t -f NAME connection show) + +if [ ${#CONNECTIONS[@]} -eq 0 ]; then + log "ERROR: No network connections found." + exit 1 +fi + +for i in "${!CONNECTIONS[@]}"; do + echo " $((i+1)). ${CONNECTIONS[$i]}" +done +echo "" + +read -p "Enter the number of the connection to configure: " CONN_CHOICE +if ! [[ "$CONN_CHOICE" =~ ^[0-9]+$ ]] || [ "$CONN_CHOICE" -lt 1 ] || [ "$CONN_CHOICE" -gt "${#CONNECTIONS[@]}" ]; then + log "ERROR: Invalid selection. Please enter a valid number from the list." + exit 1 +fi + +CONN_NAME="${CONNECTIONS[$((CONN_CHOICE-1))]}" + +echo "" +log "Current settings for '$CONN_NAME':" +CUR_METHOD=$(nmcli -g ipv4.method connection show "$CONN_NAME") +CUR_IP=$(nmcli -g IP4.ADDRESS connection show "$CONN_NAME") +if [ -z "$CUR_IP" ]; then + CUR_IP=$(nmcli -g ipv4.addresses connection show "$CONN_NAME") +fi +CUR_GW=$(nmcli -g ipv4.gateway connection show "$CONN_NAME") +CUR_DNS=$(nmcli -g ipv4.dns connection show "$CONN_NAME") +echo " - Method: ${CUR_METHOD:-}" +echo " - IP Addr (CIDR): ${CUR_IP:-}" +echo " - Gateway: ${CUR_GW:-}" +echo " - DNS: ${CUR_DNS:-}" +echo "" + +IPV4_CIDR_REGEX="^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/([0-9]|[1-2][0-9]|3[0-2])$" +IPV4_REGEX="^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$" +DNS_REGEX="^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(,[ ]?((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))*$" + +read -p "Enter static IP address with CIDR (e.g., 192.168.1.100/24): " IP_ADDR +if [ -z "$IP_ADDR" ]; then + log "ERROR: IP address cannot be empty." + exit 1 +fi + +if ! [[ "$IP_ADDR" =~ $IPV4_CIDR_REGEX ]]; then + log "ERROR: Invalid IP address format. Must be a valid IPv4 CIDR notation (e.g., 192.168.1.100/24)." + exit 1 +fi + +read -p "Enter Gateway IP address (e.g., 192.168.1.1): " GATEWAY +if [ -z "$GATEWAY" ]; then + log "ERROR: Gateway cannot be empty." + exit 1 +fi +if ! [[ "$GATEWAY" =~ $IPV4_REGEX ]]; then + log "ERROR: Invalid Gateway IP format (e.g., 192.168.1.1)." + exit 1 +fi + +read -p "Enter DNS server(s) separated by commas (e.g., 8.8.8.8,1.1.1.1): " DNS_SERVERS +if [ -z "$DNS_SERVERS" ]; then + log "ERROR: DNS servers cannot be empty." + exit 1 +fi +if ! [[ "$DNS_SERVERS" =~ $DNS_REGEX ]]; then + log "ERROR: Invalid DNS server format. Must be comma-separated IPv4 addresses (e.g., 8.8.8.8,1.1.1.1)." + exit 1 +fi + +log "Applying static IP configuration to '$CONN_NAME'..." +nmcli connection modify "$CONN_NAME" ipv4.method manual ipv4.addresses "$IP_ADDR" ipv4.gateway "$GATEWAY" ipv4.dns "$DNS_SERVERS" + +log "Restarting connection to apply changes (you may lose SSH briefly if connected remotely)..." +nmcli connection up "$CONN_NAME" + +log "=== Configuration Complete ===" +log "Verify your new IP address using 'ip addr show' or 'nmcli device show'." diff --git a/scripts/setup_keepalived.sh b/scripts/setup_keepalived.sh new file mode 100644 index 0000000..eefbe5e --- /dev/null +++ b/scripts/setup_keepalived.sh @@ -0,0 +1,113 @@ +#!/bin/bash +# +# Keepalived Setup Script for 5-Node Docker Cluster +# VIP: 140.44.4.70 +# Nodes: 140.44.4.71 - 140.44.4.75 +# +# Usage: sudo bash setup_keepalived.sh +# + +set -euo pipefail + +log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } + +if [ "$(id -u)" -ne 0 ]; then + log "ERROR: This script must be run as root (use sudo)." + exit 1 +fi + +VIP="140.44.4.70" +VIP_CIDR="24" +ROUTER_ID="70" +AUTH_PASS="DockerHA123!" + +log "Detecting primary network interface and IP..." +# Find interface with default route to the gateway (140.44.4.1) +IFACE=$(ip route | awk '/default/ {print $5}' | head -n1) +if [ -z "$IFACE" ]; then + log "ERROR: Could not detect default network interface." + exit 1 +fi + +# Find IP of that interface +MY_IP=$(ip -4 addr show dev "$IFACE" | awk '/inet / {print $2}' | cut -d/ -f1) +if [ -z "$MY_IP" ]; then + log "ERROR: Could not detect IP address for interface $IFACE." + exit 1 +fi + +log "Detected IP: $MY_IP on interface: $IFACE" + +# Validate IP and determine priority +case "$MY_IP" in + 140.44.4.71) PRIORITY=150; STATE="MASTER" ;; + 140.44.4.72) PRIORITY=140; STATE="BACKUP" ;; + 140.44.4.73) PRIORITY=130; STATE="BACKUP" ;; + 140.44.4.74) PRIORITY=120; STATE="BACKUP" ;; + 140.44.4.75) PRIORITY=110; STATE="BACKUP" ;; + *) + log "ERROR: This IP ($MY_IP) is not part of the expected cluster (140.44.4.71-75)." + exit 1 + ;; +esac + +log "Installing keepalived..." +if command -v apt-get &> /dev/null; then + apt-get update -y && apt-get install -y keepalived +elif command -v dnf &> /dev/null; then + dnf install -y keepalived +elif command -v yum &> /dev/null; then + yum install -y keepalived +else + log "ERROR: Unsupported package manager. Please install keepalived manually." + exit 1 +fi + +log "Configuring keepalived (State: $STATE, Priority: $PRIORITY)..." + +# Create a health check script for Docker +mkdir -p /etc/keepalived/scripts +cat << 'EOF' > /etc/keepalived/scripts/check_docker.sh +#!/bin/bash +# Returns 0 if docker is active, 1 if it is stopped/crashed +systemctl is-active --quiet docker +EOF +chmod +x /etc/keepalived/scripts/check_docker.sh + +# Backup existing config if any +[ -f /etc/keepalived/keepalived.conf ] && mv /etc/keepalived/keepalived.conf "/etc/keepalived/keepalived.conf.bak.$(date +%s)" + +# Create new config +cat << EOF > /etc/keepalived/keepalived.conf +vrrp_script chk_docker { + script "/etc/keepalived/scripts/check_docker.sh" + interval 2 + weight -20 +} + +vrrp_instance VI_1 { + state $STATE + interface $IFACE + virtual_router_id $ROUTER_ID + priority $PRIORITY + advert_int 1 + authentication { + auth_type PASS + auth_pass $AUTH_PASS + } + virtual_ipaddress { + $VIP/$VIP_CIDR dev $IFACE + } + track_script { + chk_docker + } +} +EOF + +log "Restarting and enabling keepalived service..." +systemctl enable keepalived +systemctl restart keepalived + +log "=== Keepalived setup complete on $MY_IP ===" +log "Check status with: systemctl status keepalived" +log "Virtual IP $VIP will be active on the node with the highest priority." diff --git a/scripts/setup_storage_cluster.sh b/scripts/setup_storage_cluster.sh new file mode 100644 index 0000000..84b43db --- /dev/null +++ b/scripts/setup_storage_cluster.sh @@ -0,0 +1,92 @@ +#!/bin/bash +# +# GlusterFS and NFS Cluster Setup Script +# Configures a 3-node replica GlusterFS volume and mounts it + NFS across all 5 Swarm nodes. +# + +set -euo pipefail + +# Cluster Configuration +ALL_NODES=("140.44.4.71" "140.44.4.72" "140.44.4.73" "140.44.4.74" "140.44.4.75") +STORAGE_NODES=("140.44.4.71" "140.44.4.72" "140.44.4.73") # Nodes holding the actual data bricks + +USER="snarf" +PASS="Katzir476!" + +# NFS Configuration +NFS_SERVER="14.10.10.71" +NFS_SHARE="/volume1/docker" # Based on your nginxproxy.yml context + +if ! command -v sshpass &> /dev/null; then + echo "ERROR: 'sshpass' is required. Please install it first." + exit 1 +fi + +# Helper function to run sudo commands remotely via sshpass +run_remote() { + local node=$1 + local cmd=$2 + echo " [${node}] Running command..." + sshpass -p "$PASS" ssh -o StrictHostKeyChecking=no "$USER@$node" "echo '$PASS' | sudo -S bash -c '$cmd'" +} + +echo "=== Phase 1: Installing Prerequisites & Creating Directories ===" +for NODE in "${ALL_NODES[@]}"; do + echo "-> Configuring Node $NODE" + run_remote "$NODE" " + apt-get update -y && \ + apt-get install -y glusterfs-server glusterfs-client nfs-common && \ + systemctl enable --now glusterd && \ + mkdir -p /data/glusterfs/swarm/brick && \ + mkdir -p /mnt/swarm_shared && \ + mkdir -p /mnt/nfs_shares + " +done + +echo "" +echo "=== Phase 2: Peering GlusterFS Storage Nodes ===" +PRIMARY_NODE="${STORAGE_NODES[0]}" + +# Peer node 2 and 3 from node 1 +for i in {1..2}; do + PEER_NODE="${STORAGE_NODES[$i]}" + echo "-> Peering $PRIMARY_NODE with $PEER_NODE" + run_remote "$PRIMARY_NODE" "gluster peer probe $PEER_NODE" +done + +# Give peering a few seconds to stabilize +sleep 5 + +echo "" +echo "=== Phase 3: Creating and Starting GlusterFS Volume ===" +echo "-> Creating 'replica 3' volume 'swarm_vols'..." +run_remote "$PRIMARY_NODE" " + gluster volume create swarm_vols replica 3 \ + ${STORAGE_NODES[0]}:/data/glusterfs/swarm/brick \ + ${STORAGE_NODES[1]}:/data/glusterfs/swarm/brick \ + ${STORAGE_NODES[2]}:/data/glusterfs/swarm/brick \ + force || echo 'Volume might already exist.' +" + +echo "-> Starting volume 'swarm_vols'..." +run_remote "$PRIMARY_NODE" "gluster volume start swarm_vols || echo 'Volume already started.'" + +echo "" +echo "=== Phase 4: Mounting GlusterFS and NFS on ALL Nodes ===" +for NODE in "${ALL_NODES[@]}"; do + echo "-> Mounting file systems on $NODE" + run_remote "$NODE" " + # Remove old fstab entries to prevent duplicates if script is re-run + sed -i '/swarm_vols/d' /etc/fstab + sed -i '/nfs_shares/d' /etc/fstab + + # Add to fstab + echo 'localhost:/swarm_vols /mnt/swarm_shared glusterfs defaults,_netdev 0 0' >> /etc/fstab + echo '$NFS_SERVER:$NFS_SHARE /mnt/nfs_shares nfs defaults,nfsvers=4,_netdev 0 0' >> /etc/fstab + + # Mount them + mount -a + " +done + +echo "=== Storage Cluster Setup Complete ===" \ No newline at end of file diff --git a/scripts/sync_legacy_volumes.sh b/scripts/sync_legacy_volumes.sh new file mode 100644 index 0000000..e9ea039 --- /dev/null +++ b/scripts/sync_legacy_volumes.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# +# Legacy Docker Volumes Sync Script +# Migrates persistent data from the legacy Swarm cluster to the new GlusterFS storage. +# + +set -euo pipefail + +# Legacy Cluster Details +LEGACY_HOST="140.44.4.31" +LEGACY_USER="dietpi" +LEGACY_PASS="Katzir476!" + +# New Cluster Details (Using node .71 as the entry point for GlusterFS) +NEW_HOST="140.44.4.71" +NEW_USER="snarf" +NEW_PASS="Katzir476!" + +if ! command -v sshpass &> /dev/null; then + echo "ERROR: 'sshpass' is required. Please install it locally first." + exit 1 +fi + +echo "=== Preparing Destination Directory and Askpass ===" +sshpass -p "$NEW_PASS" ssh -o StrictHostKeyChecking=no "$NEW_USER@$NEW_HOST" " + echo '$NEW_PASS' | sudo -S mkdir -p /mnt/swarm_shared/legacy_volumes + echo '#!/bin/bash' > /tmp/askpass.sh + echo 'echo \"$NEW_PASS\"' >> /tmp/askpass.sh + chmod +x /tmp/askpass.sh +" + +echo "=== Initiating Remote-to-Remote Rsync ===" +echo "Connecting to Legacy Cluster to start the transfer..." + +# We execute a block of commands on the legacy server to perform the sync. +# By using --rsync-path="... sudo rsync", we ensure file UIDs and GIDs are perfectly preserved. +sshpass -p "$LEGACY_PASS" ssh -o StrictHostKeyChecking=no "$LEGACY_USER@$LEGACY_HOST" " + cat << 'EOF' > /tmp/run_rsync.sh +#!/bin/bash +N_PASS=\"\$1\" +N_USER=\"\$2\" +N_HOST=\"\$3\" + +echo '-> Installing rsync and sshpass on the Legacy Cluster...' +apt-get update -y > /dev/null +apt-get install -y rsync sshpass openssh-client > /dev/null + +echo '-> Configuring Docker data directory on Legacy Cluster...' +VOLUMES_DIR=\"/mnt/docker/\" +echo \"-> Source Volumes directory manually set to: \${VOLUMES_DIR}\" + +echo '-> Starting Rsync Transfer (This may take a while depending on data size)...' +sshpass -p \"\$N_PASS\" rsync -avzh --progress \\ + --rsync-path=\"SUDO_ASKPASS=/tmp/askpass.sh sudo -A rsync\" \\ + -e \"ssh -o StrictHostKeyChecking=no\" \\ + \"\${VOLUMES_DIR}\" \"\${N_USER}@\${N_HOST}:/mnt/swarm_shared/legacy_volumes/\" +EOF + + chmod +x /tmp/run_rsync.sh + echo \"$LEGACY_PASS\" | sudo -S /tmp/run_rsync.sh \"$NEW_PASS\" \"$NEW_USER\" \"$NEW_HOST\" + rm /tmp/run_rsync.sh +" + +echo "=== Cleaning up ===" +sshpass -p "$NEW_PASS" ssh -o StrictHostKeyChecking=no "$NEW_USER@$NEW_HOST" "rm -f /tmp/askpass.sh" + +echo "=== Volume Sync Complete! ===" diff --git a/scripts/uninstall_docker_rpi.sh b/scripts/uninstall_docker_rpi.sh new file mode 100644 index 0000000..0677590 --- /dev/null +++ b/scripts/uninstall_docker_rpi.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# +# Docker Uninstallation Script for Raspberry Pi OS +# Usage: sudo bash uninstall_docker_rpi.sh +# + +set -euo pipefail + +log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } + +if [ "$(id -u)" -ne 0 ]; then + log "ERROR: This script must be run as root (use sudo)." + exit 1 +fi + +log "Stopping Docker and Containerd services..." +systemctl stop docker.socket || true +systemctl stop docker || true +systemctl stop containerd || true + +log "Uninstalling Docker packages..." +# Purge official Docker packages as well as any distribution-provided ones +apt-get purge -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras docker.io docker-doc docker-compose podman-docker runc || true + +log "Cleaning up unused dependencies..." +apt-get autoremove -y --purge + +log "Removing Docker data, images, volumes, and configurations..." +rm -rf /var/lib/docker +rm -rf /var/lib/containerd +rm -rf /etc/docker +rm -rf /var/run/docker.sock + +log "Removing the 'docker' user group..." +getent group docker > /dev/null && groupdel docker || true + +log "=== Docker Uninstallation Complete ===" +log "Your system is clean and ready to test the installation script." \ No newline at end of file