#!/usr/bin/env bash set -euo pipefail # Ensure we're in a flakes-enabled environment with required tools if ! command -v nix &> /dev/null; then echo "❌ Nix is not installed. Please install Nix first:" echo "curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install" exit 1 fi # Check if we need to enter a new shell if [ -z "${INSIDE_NIX_SHELL+x}" ]; then export NIX_CONFIG="experimental-features = nix-command flakes" export INSIDE_NIX_SHELL=1 exec nix shell nixpkgs#git nixpkgs#mkpasswd nixpkgs#jq --command bash "$0" fi # Check directory situation and handle navigation DIR_NAME="self-host-playbook" CURRENT_DIR=$(basename "$(pwd)") if [ "$CURRENT_DIR" = "$DIR_NAME" ]; then echo "📂 Already in $DIR_NAME directory" echo "âš ī¸ Warning: Proceeding will overwrite the current version!" read -p "Do you want to continue? (y/N) " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then echo "❌ Operation cancelled" exit 1 fi else if [ -d "$DIR_NAME" ]; then echo "📂 Directory '$DIR_NAME' already exists" read -p "Do you want to proceed in the existing directory? (y/N) " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then cd "$DIR_NAME" else echo "❌ Please choose a different directory or remove the existing one" exit 1 fi else echo "🔄 Creating new self-host-playbook configuration..." mkdir -p "$DIR_NAME" cd "$DIR_NAME" fi fi get_latest_version() { local LATEST_VERSION latest_version=$(curl -s "https://code.m3tam3re.com/api/v1/repos/m3tam3re/self-host-playbook/tags" | jq -r '.[] | select(.name | startswith("v")) | .name' | sort -V | tail -n1) if [ -z "$latest_version" ]; then echo "❌ Error: Could not fetch latest version from repository" exit 1 fi # Remove 'v' prefix if present and return echo "${latest_version#v}" } setup_latest_version() { local target_version=$1 local dir_name=$2 echo "âŦ‡ī¸ Downloading version $target_version..." TEMP_DIR=$(mktemp -d) trap 'rm -rf "$TEMP_DIR"' EXIT # Create a subdirectory for the clone CLONE_DIR="${TEMP_DIR}/clone" mkdir -p "$CLONE_DIR" # Clone to temporary directory with --quiet flag if ! nix flake clone --quiet "git+https://code.m3tam3re.com/m3tam3re/self-host-playbook?ref=v${target_version}" --dest "$CLONE_DIR" 2>/dev/null; then echo "❌ Failed to clone repository" return 1 fi # Copy files from clone to target directory cp -r "$CLONE_DIR"/* "$dir_name/" return 0 } # Function to setup from template setup_from_template() { # Create backup if directory is not empty if [ -n "$(ls -A)" ]; then local CURRENT_VERSION=$(date +%Y%m%d_%H%M%S) local backup_dir="backup_${CURRENT_VERSION}_$(date +%Y%m%d_%H%M%S)" echo "📑 Creating backup in $backup_dir..." mkdir -p "$backup_dir" find . -maxdepth 1 ! -name "." ! -name ".." ! -name "$backup_dir" -exec cp -r {} "$backup_dir/" \; echo "✅ Backup created successfully" # Clean current directory except backup echo "đŸ—‘ī¸ Cleaning current directory..." find . -maxdepth 1 ! -name "." ! -name ".." ! -name "$backup_dir" -exec rm -rf {} \; fi # Get and setup latest version local LATEST_VERSION=$(get_latest_version) echo "âŦ‡ī¸ Setting up version $LATEST_VERSION..." setup_latest_version "$LATEST_VERSION" "." } # Function to generate SSH key generate_ssh_key() { local KEY_NAME="self-host-playbook" local KEY_PATH="$HOME/.ssh/${KEY_NAME}" W if [ ! -f "$KEY_PATH" ]; then mkdir -p "$HOME/.ssh" echo "🔑 Generating new SSH key pair..." >&2 ssh-keygen -t ed25519 -f "$KEY_PATH" -N "" >&2 echo "✅ New SSH key pair generated" >&2 else echo "🔑 Using existing SSH key pair" >&2 fi if [ ! -f "${KEY_PATH}.pub" ]; then echo "❌ Error: Public key file not found" >&2 exit 1 fi printf "%s" "$KEY_PATH" } # Function to hash password using mkpasswd hash_password() { local password=$1 mkpasswd -m sha-512 "$password" } # Function to display device detection guide show_device_guide() { echo echo "📝 How to find your root device name:" echo "------------------------------------" echo "1. SSH into your server with the credentials provided by your cloud provider or use the web console" echo "2. Run the following command:" echo " lsblk -d -o NAME,SIZE" echo echo "Look for the main disk, usually the largest one. It will be shown as:" echo "- AWS (older): xvda" echo "- AWS (newer): nvme0n1" echo "- GCP/Azure/Linode: sda" echo "- DigitalOcean: vda" echo echo "The device name will be /dev/[name shown in lsblk]" echo echo "💡 Example:" echo " NAME SIZE" echo " sda 76.3G" echo " sr0 1024M" echo "------------------------------------" echo } # Function to get device name based on provider get_device_name() { local provider=$1 case $provider in "aws-new") echo "/dev/nvme0n1" ;; "aws-old") echo "/dev/xvda" ;; "gcp") echo "/dev/sda" ;; "azure") echo "/dev/sda" ;; "digitalocean") echo "/dev/vda" ;; "linode") echo "/dev/sda" ;; "hetzner") echo "/dev/sda" ;; *) echo "unknown" ;; esac } setup_ssh_config() { local username=$1 local ip_address=$2 local ssh_config_dir="$HOME/.ssh" local ssh_config_file="$ssh_config_dir/config" local ssh_key_file="$ssh_config_dir/self-host-playbook" # Create .ssh directory if it doesn't exist mkdir -p "$ssh_config_dir" chmod 700 "$ssh_config_dir" # Create or append to SSH config local config_entry="Host self-host-playbook HostName $ip_address User $username IdentityFile $ssh_key_file" # Check if entry already exists if ! grep -q "Host self-host-playbook" "$ssh_config_file" 2>/dev/null; then echo -e "\n$config_entry" >> "$ssh_config_file" echo "✅ Added SSH config entry" else # Update existing entry sed -i.bak "/Host self-host-playbook/,/IdentityFile.*/{ s/HostName.*/HostName $ip_address/ s/User.*/User $username/ }" "$ssh_config_file" echo "✅ Updated existing SSH config entry" fi # Set appropriate permissions chmod 600 "$ssh_config_file" } install_deploy_rs() { echo "🔧 Installing deploy-rs to user environment..." # Check if deploy is already installed if command -v deploy >/dev/null 2>&1; then echo "â„šī¸ deploy-rs is already installed" return 0 fi # Install deploy-rs using nix profile if nix profile install 'github:serokell/deploy-rs'; then echo "✅ deploy-rs installed successfully" else echo "❌ Failed to install deploy-rs" return 1 fi } echo "🚀 Welcome to the Self-Host Playbook!" echo "================================================" echo "This script will help you manage your NixOS server with:" echo "- Portainer (Docker management)" echo "- n8n (Workflow automation)" echo "- Baserow (No-code database)" echo "- Caddy (Automatic HTTPS reverse proxy)" echo "================================================" echo read -p "Press ANY KEY to continue or CTRL + C to abort..." echo # Generate SSH key SSH_KEY_PATH=$(generate_ssh_key) || exit 1 SSH_PUB_KEY=$(cat "${SSH_KEY_PATH}.pub") || { echo "❌ Error: Failed to read public key from ${SSH_KEY_PATH}.pub" exit 1 } echo echo "🔑 Here is your public key:" echo cat $SSH_KEY_PATH.pub echo "" echo "📁 You can also find the keyfile here:" echo echo $SSH_KEY_PATH.pub echo read -p "Press ENTER to continue or CTRL + C to abort..." # Collect user input echo "" echo "📝 Please provide the following information:" echo "-------------------------------------------" read -p "1. Enter target server IP address: " IP_ADDRESS read -p "2. Enter desired username for server access: " USERNAME read -s -p "3. Enter desired password: " PASSWORD echo echo "4. Enter domain names for services (must point to $IP_ADDRESS):" read -p " - Domain for Portainer: " PORTAINER_DOMAIN read -p " - Domain for n8n: " N8N_DOMAIN read -p " - Domain for Baserow: " BASEROW_DOMAIN echo echo "5. How do you authentiate to the target machine?" echo "-------------------------------------------" echo " 1) Password" echo " 2) SSH Key" echo read -p "Enter your choice (1-2): " KEY_CHOICE case $KEY_CHOICE in 1) INSTALL_COMMAND="nix run github:nix-community/nixos-anywhere -- --flake .#server root@$IP_ADDRESS" ;; 2) INSTALL_COMMAND="nix run github:nix-community/nixos-anywhere -- --flake .#server -i $SSH_KEY_PATH root@$IP_ADDRESS" ;; *) echo "❌ Invalid choice" exit 1 ;; esac setup_from_template echo echo "6. Select your cloud provider:" echo " 1) AWS (Newer instances with NVMe)" echo " 2) AWS (Older instances)" echo " 3) Google Cloud Platform" echo " 4) Microsoft Azure" echo " 5) DigitalOcean" echo " 6) Linode" echo " 7) Hetzner" echo " 8) I'm not sure (Show detection guide)" echo read -p "Enter your choice (1-8): " PROVIDER_CHOICE # Set device name based on provider choice case $PROVIDER_CHOICE in 1) PROVIDER="aws-new" ;; 2) PROVIDER="aws-old" ;; 3) PROVIDER="gcp" ;; 4) PROVIDER="azure" ;; 5) PROVIDER="digitalocean" ;; 6) PROVIDER="linode" ;; 7) PROVIDER="hetzner" ;; 8) show_device_guide read -p "Enter your root device name (e.g., /dev/sda): " DEVICE_NAME PROVIDER="custom" ;; *) echo "❌ Invalid choice" exit 1 ;; esac if [ "$PROVIDER" != "custom" ]; then DEVICE_NAME=$(get_device_name "$PROVIDER") fi echo echo "Using root device: $DEVICE_NAME" read -p "Is this correct? (y/N) " CONFIRM if [[ ! $CONFIRM =~ ^[Yy]$ ]]; then echo "Installation aborted. Please run the script again with the correct device." exit 1 fi echo echo "đŸ› ī¸ Preparing server configuration..." # Hash the password HASHED_PASSWORD=$(hash_password "$PASSWORD") echo "📝 Customizing configuration files..." # Write configuration to JSON file cat > config.json << EOF { "username": "$USERNAME", "hashedPassword": "$HASHED_PASSWORD", "sshKey": "$SSH_PUB_KEY", "domains": { "portainer": "$PORTAINER_DOMAIN", "n8n": "$N8N_DOMAIN", "baserow": "$BASEROW_DOMAIN" }, "rootDevice": "$DEVICE_NAME", "ipAddress": "$IP_ADDRESS" } EOF echo "đŸ“Ļ Setting up environment files..." # Update environment files with domains if [[ "$OSTYPE" == "darwin"* ]]; then sed -i '' "s/N8N_DOMAIN/$N8N_DOMAIN/g" ./env/n8n.env sed -i '' "s/BASEROW_DOMAIN/$BASEROW_DOMAIN/g" ./env/baserow.env else sed -i "s/N8N_DOMAIN/$N8N_DOMAIN/g" ./env/n8n.env sed -i "s/BASEROW_DOMAIN/$BASEROW_DOMAIN/g" ./env/baserow.env fi echo echo "âš ī¸ Important: By proceeding the existing virtual machine will be overwritten!" echo read -p "Do you want to proceed? (y/N) " CONFIRM if [[ ! $CONFIRM =~ ^[Yy]$ ]]; then echo "Installation aborted." exit 1 fi echo echo "🚀 Starting NixOS installation..." echo "This process might take several minutes..." # Run nixos-anywhere installation $INSTALL_COMMAND && { echo "🔧 Setting up SSH configuration..." setup_ssh_config "$USERNAME" "$IP_ADDRESS" echo echo "🎉 Installation completed successfully!" echo "=====================================>" echo "You can now access your services at:" echo "- Portainer: https://$PORTAINER_DOMAIN" echo "- n8n: https://$N8N_DOMAIN" echo "- Baserow: https://$BASEROW_DOMAIN" echo echo "To connect to your server, use:" echo "ssh self-host-playbook" echo install_deploy_rs echo echo "âš ī¸ Important: Please save your SSH key path: $SSH_KEY_PATH" echo "=====================================>" } || echo "Command failed with exit status $?"