#!/usr/bin/env bash set -euo pipefail # EveryTab EC2 Bootstrap # Run this on the EC2 instance after first SSH connection. # Installs: Go, DuckDB, Unbound, psql, pg_dump echo "=== EveryTab EC2 Bootstrap ===" # --- Swap --- echo "--- Creating swap file ---" if [ ! -f /swapfile ]; then sudo dd if=/dev/zero of=/swapfile bs=1M count=4096 sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile echo '/swapfile swap swap defaults 0 0' | sudo tee -a /etc/fstab echo "Created 4GB swap" else echo "Swap already exists" fi # --- System packages --- echo "--- Installing system packages ---" sudo dnf update -y sudo dnf install -y \ gcc \ git \ postgresql16 \ unbound \ jq \ htop \ tmux # --- Go --- echo "--- Installing Go ---" GO_VERSION="1.22.4" if ! command -v go &>/dev/null; then curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" | sudo tar -C /usr/local -xz echo 'export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin' >> ~/.bashrc export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin fi go version # --- DuckDB --- echo "--- Installing DuckDB ---" DUCKDB_VERSION="1.5.2" if ! command -v duckdb &>/dev/null; then curl -fsSL "https://github.com/duckdb/duckdb/releases/download/v${DUCKDB_VERSION}/duckdb_cli-linux-amd64.zip" -o /tmp/duckdb.zip cd /tmp && unzip -o duckdb.zip && sudo mv duckdb /usr/local/bin/ && cd - fi duckdb -c "SELECT 'DuckDB OK';" # Install DuckDB extensions duckdb -c "INSTALL httpfs; INSTALL postgres;" echo "DuckDB extensions installed" # --- Unbound --- echo "--- Configuring Unbound ---" sudo tee /etc/unbound/unbound.conf > /dev/null <<'UNBOUNDCONF' server: interface: 127.0.0.1 port: 53 access-control: 127.0.0.0/8 allow # Performance num-threads: 4 msg-cache-slabs: 4 rrset-cache-slabs: 4 infra-cache-slabs: 4 key-cache-slabs: 4 # Cache sizing (use available RAM) msg-cache-size: 512m rrset-cache-size: 1g key-cache-size: 256m # Aggressive caching cache-min-ttl: 3600 cache-max-ttl: 86400 prefetch: yes prefetch-key: yes # Hardening hide-identity: yes hide-version: yes harden-glue: yes harden-dnssec-stripped: yes # Logging (minimal) verbosity: 1 log-queries: no # Root hints root-hints: "/etc/unbound/root.hints" remote-control: control-enable: yes control-interface: 127.0.0.1 UNBOUNDCONF # Download root hints sudo curl -fsSL https://www.internic.net/domain/named.root -o /etc/unbound/root.hints # Disable systemd-resolved if present (it manages resolv.conf on AL2023) if systemctl is-active --quiet systemd-resolved 2>/dev/null; then sudo systemctl disable --now systemd-resolved fi # Set system resolver to use Unbound sudo rm -f /etc/resolv.conf echo "nameserver 127.0.0.1" | sudo tee /etc/resolv.conf > /dev/null # Start and enable Unbound sudo systemctl enable unbound sudo systemctl restart unbound # Generate control keys for unbound-control stats sudo unbound-control-setup 2>/dev/null || true echo "" # --- Validation --- echo "=== Validation ===" echo -n "Go: "; go version echo -n "DuckDB: "; duckdb -c "SELECT version();" -noheader -csv echo -n "Unbound: "; dig +short example.com @127.0.0.1 | head -1 echo -n "psql: "; psql --version echo "" echo "=== Bootstrap Complete ===" echo "" echo "Next: set up your database connection string." echo " export DATABASE_URL='postgres://everytab:PASSWORD@RDS_ENDPOINT:5432/everytab'" echo "" echo "Test connection:" echo " psql \$DATABASE_URL -c 'SELECT 1;'"