mirror of
https://github.com/trailofbits/algo.git
synced 2026-10-01 05:55:06 +02:00
* feat: add destroy subcommand to tear down deployed servers Add `./algo destroy <server-ip>` to programmatically remove cloud resources and clean up local configs. Reads provider and server name from configs/<ip>/.config.yml, gathers credentials via existing prompts.yml, confirms with user, then dispatches to provider-specific destroy tasks. Supports all 11 cloud providers: - DigitalOcean, EC2, Lightsail (CloudFormation), Azure (resource group), GCE (instance + subsidiary resources), Hetzner, Vultr, Scaleway, OpenStack, CloudStack, Linode - Local provider: config cleanup only Also stores algo_region in .config.yml during deployment so destroy can auto-detect region. Fixes Scaleway module to allow state=absent without image/commercial_type/organization params. Adds Vultr to region-required providers and stores algo_region in Vultr prompts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add list-servers script and tests Add scripts/list_servers.py to scan configs/ for deployed server metadata and output JSON. Referenced by `./algo list-servers`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
212 lines
8.3 KiB
Bash
Executable File
212 lines
8.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -e
|
|
|
|
# Track which installation method succeeded
|
|
UV_INSTALL_METHOD=""
|
|
|
|
# Function to install uv via package managers (most secure)
|
|
install_uv_via_package_manager() {
|
|
echo "Attempting to install uv via system package manager..."
|
|
|
|
if command -v brew &> /dev/null; then
|
|
echo "Using Homebrew..."
|
|
brew install uv && UV_INSTALL_METHOD="Homebrew" && return 0
|
|
elif command -v apt &> /dev/null && apt list uv 2>/dev/null | grep -q uv; then
|
|
echo "Using apt..."
|
|
sudo apt update && sudo apt install -y uv && UV_INSTALL_METHOD="apt" && return 0
|
|
elif command -v dnf &> /dev/null; then
|
|
echo "Using dnf..."
|
|
sudo dnf install -y uv 2>/dev/null && UV_INSTALL_METHOD="dnf" && return 0
|
|
elif command -v pacman &> /dev/null; then
|
|
echo "Using pacman..."
|
|
sudo pacman -S --noconfirm uv 2>/dev/null && UV_INSTALL_METHOD="pacman" && return 0
|
|
elif command -v zypper &> /dev/null; then
|
|
echo "Using zypper..."
|
|
sudo zypper install -y uv 2>/dev/null && UV_INSTALL_METHOD="zypper" && return 0
|
|
elif command -v winget &> /dev/null; then
|
|
echo "Using winget..."
|
|
winget install --id=astral-sh.uv -e && UV_INSTALL_METHOD="winget" && return 0
|
|
elif command -v scoop &> /dev/null; then
|
|
echo "Using scoop..."
|
|
scoop install uv && UV_INSTALL_METHOD="scoop" && return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
# Function to handle Ubuntu-specific installation alternatives
|
|
install_uv_ubuntu_alternatives() {
|
|
# Check if we're on Ubuntu
|
|
if ! command -v lsb_release &> /dev/null || [[ "$(lsb_release -si)" != "Ubuntu" ]]; then
|
|
return 1 # Not Ubuntu, skip these options
|
|
fi
|
|
|
|
echo ""
|
|
echo "Ubuntu detected. Additional trusted installation options available:"
|
|
echo ""
|
|
echo "1. pipx (official PyPI, installs ~9 packages)"
|
|
echo " Command: sudo apt install pipx && pipx install uv"
|
|
echo ""
|
|
echo "2. snap (community-maintained by Canonical employee)"
|
|
echo " Command: sudo snap install astral-uv --classic"
|
|
echo " Source: https://github.com/lengau/uv-snap"
|
|
echo ""
|
|
echo "3. Continue to official installer script download"
|
|
echo ""
|
|
|
|
while true; do
|
|
read -r -p "Choose installation method (1/2/3): " choice
|
|
case $choice in
|
|
1)
|
|
echo "Installing uv via pipx..."
|
|
if sudo apt update && sudo apt install -y pipx; then
|
|
if pipx install uv; then
|
|
# Add pipx bin directory to PATH
|
|
export PATH="$HOME/.local/bin:$PATH"
|
|
UV_INSTALL_METHOD="pipx"
|
|
return 0
|
|
fi
|
|
fi
|
|
echo "pipx installation failed, trying next option..."
|
|
;;
|
|
2)
|
|
echo "Installing uv via snap..."
|
|
if sudo snap install astral-uv --classic; then
|
|
# Snap binaries should be automatically in PATH via /snap/bin
|
|
UV_INSTALL_METHOD="snap"
|
|
return 0
|
|
fi
|
|
echo "snap installation failed, trying next option..."
|
|
;;
|
|
3)
|
|
return 1 # Continue to official installer download
|
|
;;
|
|
*)
|
|
echo "Invalid option. Please choose 1, 2, or 3."
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Function to install uv via download (with user consent)
|
|
install_uv_via_download() {
|
|
echo ""
|
|
echo "⚠️ SECURITY NOTICE ⚠️"
|
|
echo "uv is not available via system package managers on this system."
|
|
echo "To continue, we need to download and execute an installation script from:"
|
|
echo " https://astral.sh/uv/install.sh (Linux/macOS)"
|
|
echo " https://astral.sh/uv/install.ps1 (Windows)"
|
|
echo ""
|
|
echo "For maximum security, you can install uv manually instead:"
|
|
echo " 1. Visit: https://docs.astral.sh/uv/getting-started/installation/"
|
|
echo " 2. Download the binary for your platform from GitHub releases"
|
|
echo " 3. Verify checksums and install manually"
|
|
echo " 4. Then run: ./algo"
|
|
echo ""
|
|
|
|
read -p "Continue with script download? (y/N): " -n 1 -r
|
|
echo
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
echo "Installation cancelled. Please install uv manually and retry."
|
|
exit 1
|
|
fi
|
|
|
|
echo "Downloading uv installation script..."
|
|
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OSTYPE" == "linux-gnu" && -n "${WSL_DISTRO_NAME:-}" ]] || uname -s | grep -q "MINGW\|MSYS"; then
|
|
# Windows (Git Bash/WSL/MINGW) - use versioned installer
|
|
powershell -ExecutionPolicy ByPass -c "irm https://github.com/astral-sh/uv/releases/download/0.8.5/uv-installer.ps1 | iex"
|
|
UV_INSTALL_METHOD="official installer (Windows)"
|
|
else
|
|
# macOS/Linux - use the versioned script for consistency
|
|
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.8.5/uv-installer.sh | sh
|
|
UV_INSTALL_METHOD="official installer"
|
|
fi
|
|
}
|
|
|
|
# Check if uv is installed, if not, install it securely
|
|
if ! command -v uv &> /dev/null; then
|
|
echo "uv (Python package manager) not found. Installing..."
|
|
|
|
# Try package managers first (most secure)
|
|
if ! install_uv_via_package_manager; then
|
|
# Try Ubuntu-specific alternatives if available
|
|
if ! install_uv_ubuntu_alternatives; then
|
|
# Fall back to download with user consent
|
|
install_uv_via_download
|
|
fi
|
|
fi
|
|
|
|
# Reload PATH to find uv (includes pipx, cargo, and snap paths)
|
|
# Note: This PATH change only affects the current shell session.
|
|
# Users may need to restart their terminal for subsequent runs.
|
|
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:/snap/bin:$PATH"
|
|
|
|
# Verify installation worked
|
|
if ! command -v uv &> /dev/null; then
|
|
echo "Error: uv installation failed. Please restart your terminal and try again."
|
|
echo "Or install manually from: https://docs.astral.sh/uv/getting-started/installation/"
|
|
exit 1
|
|
fi
|
|
|
|
echo "✓ uv installed successfully via ${UV_INSTALL_METHOD}!"
|
|
fi
|
|
|
|
# Install Ansible Galaxy collections if requirements.yml exists
|
|
# This is needed for cloud providers that use collection modules (Linode, DigitalOcean, Azure, etc.)
|
|
if [ -f "requirements.yml" ]; then
|
|
uv run ansible-galaxy collection install -r requirements.yml > /dev/null 2>&1 || true
|
|
fi
|
|
|
|
# Run the appropriate playbook
|
|
case "$1" in
|
|
help|-h|--help)
|
|
echo "Usage: ./algo [COMMAND] [ANSIBLE_OPTIONS]"
|
|
echo ""
|
|
echo "Set up a personal VPN in the cloud."
|
|
echo ""
|
|
echo "Commands:"
|
|
echo " (default) Deploy a new VPN server"
|
|
echo " update-users Add or remove users on an existing server"
|
|
echo " destroy Destroy a deployed server and clean up configs"
|
|
echo " list-servers List deployed servers (JSON output)"
|
|
echo ""
|
|
echo "Configuration:"
|
|
echo " Edit config.cfg to set users, DNS, and VPN options before deploying."
|
|
echo ""
|
|
echo "Non-interactive deployment:"
|
|
echo " ./algo -e 'provider=digitalocean server_name=algo region=nyc3 do_token=TOKEN'"
|
|
echo ""
|
|
echo "Common Ansible options (passed through):"
|
|
echo " -e KEY=VALUE Set variable (bypass interactive prompts)"
|
|
echo " -v, -vvv Increase output verbosity"
|
|
echo " --skip-tags TAG Skip specific components"
|
|
echo " -t, --tags TAG Run only specific components"
|
|
echo ""
|
|
echo "Docs: https://trailofbits.github.io/algo/"
|
|
exit 0
|
|
;;
|
|
update-users)
|
|
uv run ansible-playbook users.yml "${@:2}" -t update-users ;;
|
|
destroy)
|
|
if [ -z "${2:-}" ] || [[ "$2" == -* ]]; then
|
|
echo "Usage: ./algo destroy <server-ip> [ANSIBLE_OPTIONS]"
|
|
echo ""
|
|
echo "Destroy a deployed Algo VPN server and remove local configs."
|
|
echo ""
|
|
echo "Arguments:"
|
|
echo " server-ip IP address of the server to destroy"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " ./algo destroy 188.166.66.185"
|
|
echo " ./algo destroy 52.1.2.3 -e \"region=us-east-1\""
|
|
echo " ./algo destroy 188.166.66.185 -e \"confirm_destroy=true\""
|
|
exit 1
|
|
fi
|
|
uv run ansible-playbook destroy.yml -e "server_ip=$2" "${@:3}" ;;
|
|
list-servers)
|
|
uv run python3 scripts/list_servers.py "${@:2}" ;;
|
|
*)
|
|
uv run ansible-playbook main.yml "${@}" ;;
|
|
esac
|