2025-04-22 08:44:10 +02:00

474 lines
14 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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.m3ta.dev/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
# Safety check: Ensure we're in the self-host-playbook directory
local current_dir=$(basename "$(pwd)")
if [ "$current_dir" != "self-host-playbook" ]; then
echo "❌ Error: Must be in 'self-host-playbook' directory to setup latest version"
echo "Current directory: $(pwd)"
return 1
fi
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.m3ta.dev/m3tam3re/self-host-playbook?ref=v${target_version}" --dest "$CLONE_DIR" 2>/dev/null; then
echo "❌ Failed to clone repository"
return 1
fi
# Additional safety check before copying files
if [ ! -f "$CLONE_DIR/flake.nix" ]; then
echo "❌ Error: Downloaded content doesn't appear to be a valid self-host-playbook"
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() {
# Ensure we're in the correct directory
local current_dir=$(basename "$(pwd)")
if [ "$current_dir" != "self-host-playbook" ]; then
echo "❌ Error: Must be in 'self-host-playbook' directory"
exit 1
fi
# 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"
# Add safety check for backup creation
if [ ! -d "$backup_dir" ]; then
echo "❌ Error: Failed to create backup directory"
exit 1
fi
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}"
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 hostname=$3 # Add hostname parameter
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 using hostname as the Host name
local config_entry="Host $hostname
HostName $ip_address
User $username
Port 2222
IdentityFile $ssh_key_file"
# Check if entry already exists
if ! grep -q "Host $hostname" "$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 $hostname/,/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 hostname for the server: " HOSTNAME
# Validate hostname format
while ! [[ $HOSTNAME =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$ ]]; do
echo "❌ Invalid hostname format. Please use a valid hostname (e.g., my-server.example.com)"
read -p "Enter hostname for the server: " HOSTNAME
done
read -p "3. Enter desired username for server access: " USERNAME
read -s -p "4. Enter desired password: " PASSWORD
echo
echo "5. 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 "6. How do you authenticate 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 .#$HOSTNAME root@$IP_ADDRESS"
;;
2)
INSTALL_COMMAND="nix run github:nix-community/nixos-anywhere -- --flake .#$HOSTNAME -i $SSH_KEY_PATH root@$IP_ADDRESS"
;;
*)
echo "❌ Invalid choice"
exit 1
;;
esac
setup_from_template
echo
echo "7. 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",
"hostname": "$HOSTNAME"
}
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" "$HOSTNAME"
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 $HOSTNAME"
echo
install_deploy_rs
echo
echo "⚠️ Important: Please save your SSH key path: $SSH_KEY_PATH"
echo "=====================================>"
} || echo "Command failed with exit status $?"