Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
11 kB · 360 lines
Shell
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361#!/usr/bin/env bash# Aesthetic Computer PDS Deployment Script - DigitalOcean# This script automates the deployment of a PDS server on DigitalOcean
set -euo pipefail
# Colors for outputRED='\033[0;31m'GREEN='\033[0;32m'YELLOW='\033[1;33m'BLUE='\033[0;34m'NC='\033[0m' # No Color
# ConfigurationSCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"VAULT_DIR="$PROJECT_ROOT/../../aesthetic-computer-vault"
# Default valuesDROPLET_NAME="aesthetic-computer-at"DROPLET_SIZE="s-1vcpu-1gb" # $6/monthDROPLET_IMAGE="ubuntu-24-04-x64"DROPLET_REGION="nyc3"SSH_KEY_NAME="aesthetic-computer"DOMAIN="at.aesthetic.computer"SPACES_BUCKET="at-blobs-aesthetic-computer"
# Functions
info() { echo -e "${BLUE}ℹ${NC} $1"}
success() { echo -e "${GREEN}✓${NC} $1"}
warning() { echo -e "${YELLOW}⚠${NC} $1"}
error() { echo -e "${RED}✗${NC} $1" exit 1}
check_requirements() { info "Checking requirements..." # Check for doctl if ! command -v doctl &> /dev/null; then error "doctl not found. Install with: brew install doctl" fi # Check authentication if ! doctl account get &> /dev/null; then error "doctl not authenticated. Run: doctl auth init" fi # Check for DO token in vault if [ -f "$VAULT_DIR/.devcontainer/envs/devcontainer.env" ]; then source "$VAULT_DIR/.devcontainer/envs/devcontainer.env" if [ -z "${DO_TOKEN:-}" ]; then warning "DO_TOKEN not found in vault, will use doctl authentication" fi fi success "Requirements check passed"}
create_spaces_bucket() { info "Creating DigitalOcean Spaces bucket..." # Configure s3cmd for DigitalOcean Spaces cat > /tmp/s3cfg << EOF[default]access_key = ${SPACES_KEY}secret_key = ${SPACES_SECRET}host_base = ${DROPLET_REGION}.digitaloceanspaces.comhost_bucket = %(bucket)s.${DROPLET_REGION}.digitaloceanspaces.comuse_https = TrueEOF # Check if space already exists if s3cmd -c /tmp/s3cfg ls | grep -q "s3://$SPACES_BUCKET"; then warning "Space '$SPACES_BUCKET' already exists, skipping creation" rm /tmp/s3cfg return 0 fi # Create space using s3cmd s3cmd -c /tmp/s3cfg mb "s3://$SPACES_BUCKET" if [ $? -eq 0 ]; then success "Created Spaces bucket: $SPACES_BUCKET" else error "Failed to create Spaces bucket" fi rm /tmp/s3cfg}
create_ssh_key() { info "Setting up SSH key..." # Check if key already exists in DigitalOcean SSH_KEY_ID=$(doctl compute ssh-key list --format ID,Name --no-header | grep "$SSH_KEY_NAME" | awk '{print $1}') if [ -n "$SSH_KEY_ID" ]; then success "SSH key '$SSH_KEY_NAME' already exists (ID: $SSH_KEY_ID)" return 0 fi # Generate key if doesn't exist locally if [ ! -f ~/.ssh/aesthetic_pds ]; then ssh-keygen -t ed25519 -f ~/.ssh/aesthetic_pds -N "" -C "pds@aesthetic.computer" fi # Add to DigitalOcean doctl compute ssh-key import "$SSH_KEY_NAME" --public-key-file ~/.ssh/aesthetic_pds.pub # Get the key ID SSH_KEY_ID=$(doctl compute ssh-key list --format ID,Name --no-header | grep "$SSH_KEY_NAME" | awk '{print $1}') success "SSH key added to DigitalOcean (ID: $SSH_KEY_ID)"}
create_droplet() { info "Creating droplet..." # Check if droplet already exists if doctl compute droplet list | grep -q "$DROPLET_NAME"; then warning "Droplet '$DROPLET_NAME' already exists" # Check if running non-interactively (with AUTO_CONFIRM=yes) if [[ "$AUTO_CONFIRM" == "yes" ]]; then info "Auto-confirming droplet deletion (non-interactive mode)..." CONFIRM="y" else read -p "Delete existing droplet and recreate? (y/N) " -n 1 -r CONFIRM echo # Move to new line fi if [[ $CONFIRM =~ ^[Yy]$ ]]; then info "Deleting existing droplet..." DROPLET_ID=$(doctl compute droplet list --format ID,Name --no-header | grep "$DROPLET_NAME" | awk '{print $1}') doctl compute droplet delete "$DROPLET_ID" --force || error "Failed to delete existing droplet" success "Droplet deleted" # Wait for deletion to complete sleep 5 else error "Deployment cancelled. Please use a different droplet name or delete manually." fi fi # Create the droplet info "Creating new droplet..." DROPLET_OUTPUT=$(doctl compute droplet create "$DROPLET_NAME" \ --size "$DROPLET_SIZE" \ --image "$DROPLET_IMAGE" \ --region "$DROPLET_REGION" \ --ssh-keys "$SSH_KEY_ID" \ --enable-monitoring \ --wait \ --format ID,Name,PublicIPv4 \ --no-header) || error "Failed to create droplet" DROPLET_ID=$(echo "$DROPLET_OUTPUT" | awk '{print $1}') DROPLET_IP=$(echo "$DROPLET_OUTPUT" | awk '{print $3}') # Save droplet info echo "$DROPLET_ID" > "$SCRIPT_DIR/.droplet_id" echo "$DROPLET_IP" > "$SCRIPT_DIR/.droplet_ip" success "Droplet created: $DROPLET_NAME ($DROPLET_IP)"}
configure_dns() { info "Configuring DNS via Cloudflare API..." if [ ! -f "$SCRIPT_DIR/.droplet_ip" ]; then error "Droplet IP not found. Create droplet first." fi DROPLET_IP=$(cat "$SCRIPT_DIR/.droplet_ip") # Run the Node.js DNS configuration script node "$SCRIPT_DIR/configure-dns.mjs" || error "Failed to configure DNS" # Verify DNS propagation info "Verifying DNS propagation..." echo "" MAX_ATTEMPTS=30 ATTEMPT=0 DNS_READY=false while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do if host "$DOMAIN" | grep -q "$DROPLET_IP"; then DNS_READY=true break fi ATTEMPT=$((ATTEMPT + 1)) echo -n "." sleep 2 done echo "" if [ "$DNS_READY" = true ]; then success "DNS propagated successfully" else warning "DNS not fully propagated yet, but continuing..." warning "The PDS installer will wait for DNS to be ready" fi}
install_pds() { info "Installing PDS on droplet..." if [ ! -f "$SCRIPT_DIR/.droplet_ip" ]; then error "Droplet IP not found. Create droplet first." fi DROPLET_IP=$(cat "$SCRIPT_DIR/.droplet_ip") # Wait for SSH to be ready info "Waiting for SSH to be ready..." for i in {1..30}; do if ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -i ~/.ssh/aesthetic_pds "root@$DROPLET_IP" "echo 'SSH ready'" &> /dev/null; then success "SSH connection established" break fi if [ $i -eq 30 ]; then error "SSH connection timeout" fi sleep 10 done # Download and run PDS installer ssh -i ~/.ssh/aesthetic_pds "root@$DROPLET_IP" << 'ENDSSH'set -euo pipefail
echo "Updating system..."apt-get updateapt-get upgrade -y
echo "Downloading PDS installer..."wget -O /root/installer.sh https://raw.githubusercontent.com/bluesky-social/pds/main/installer.sh
echo "Running PDS installer..."bash /root/installer.shENDSSH success "PDS installed on droplet"}
configure_pds() { info "Configuring PDS..." if [ ! -f "$SCRIPT_DIR/.droplet_ip" ]; then error "Droplet IP not found. Create droplet first." fi DROPLET_IP=$(cat "$SCRIPT_DIR/.droplet_ip") warning "Configuration steps:" echo "" echo "1. SSH to server: ssh -i ~/.ssh/aesthetic_pds root@$DROPLET_IP" echo "2. Edit /pds/pds.env with your configuration" echo "3. Add Spaces credentials from vault" echo "4. Configure SMTP settings" echo "5. Restart PDS: systemctl restart pds" echo "" echo "See config/pds.env.example for template"}
create_firewall() { info "Creating firewall rules..." FIREWALL_NAME="pds-aesthetic-computer" # Check if firewall exists if doctl compute firewall list | grep -q "$FIREWALL_NAME"; then warning "Firewall '$FIREWALL_NAME' already exists" return 0 fi # Create firewall doctl compute firewall create \ --name "$FIREWALL_NAME" \ --inbound-rules "protocol:tcp,ports:80,address:0.0.0.0/0 protocol:tcp,ports:443,address:0.0.0.0/0 protocol:tcp,ports:22,address:0.0.0.0/0" \ --outbound-rules "protocol:tcp,ports:all,address:0.0.0.0/0 protocol:udp,ports:all,address:0.0.0.0/0" # Get droplet ID DROPLET_ID=$(doctl compute droplet list --format ID,Name --no-header | grep "$DROPLET_NAME" | awk '{print $1}') # Apply to droplet doctl compute firewall add-droplets "$FIREWALL_NAME" --droplet-ids "$DROPLET_ID" success "Firewall configured"}
main() { echo "" echo "╔════════════════════════════════════════════════════════╗" echo "║ Aesthetic Computer PDS Deployment - DigitalOcean ║" echo "╚════════════════════════════════════════════════════════╝" echo "" check_requirements echo "" info "Deployment configuration:" echo " Droplet: $DROPLET_NAME" echo " Size: $DROPLET_SIZE" echo " Region: $DROPLET_REGION" echo " Domain: $DOMAIN" echo "" if [[ "$AUTO_CONFIRM" == "yes" ]]; then info "Auto-confirming deployment (non-interactive mode)..." else read -p "Continue with deployment? (y/N) " -n 1 -r echo "" if [[ ! $REPLY =~ ^[Yy]$ ]]; then error "Deployment cancelled" fi fi echo "" create_ssh_key create_spaces_bucket create_droplet create_firewall configure_dns install_pds configure_pds echo "" success "Deployment complete!" echo "" info "Next steps:" echo "1. SSH to server: ssh -i ~/.ssh/aesthetic_pds root@$(cat "$SCRIPT_DIR/.droplet_ip")" echo "2. Configure /pds/pds.env (see config/pds.env.example)" echo "3. Restart PDS: systemctl restart pds" echo "4. Create account: pdsadmin account create" echo "5. Verify health: curl https://$DOMAIN/xrpc/_health" echo ""}
# Run main or specific functionif [ $# -gt 0 ]; then # Call specific function if provided "$@"else # Run full deployment mainfi