Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
10 kB · 367 lines
Shell
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368#!/usr/bin/env bash# Aesthetic Computer Knot Deployment Script# Installs a Tangled knot server on the existing PDS droplet (at.aesthetic.computer)# Target: knot.aesthetic.computer
set -euo pipefail
RED='\033[0;31m'GREEN='\033[0;32m'YELLOW='\033[1;33m'BLUE='\033[0;34m'NC='\033[0m'
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"VAULT_DIR="$PROJECT_ROOT/../aesthetic-computer-vault"INFRA_DIR="$SCRIPT_DIR/../infra"
# Knot configurationKNOT_DOMAIN="knot.aesthetic.computer"KNOT_PUBLIC_PORT=5555KNOT_INTERNAL_PORT=5444APPVIEW_ENDPOINT="https://tangled.org"# Tag or commit SHA from https://tangled.org/@tangled.org/core (empty = build master HEAD).# Currently pinned to a master SHA past v1.13.0-alpha — no v1.14 tag exists yet.KNOT_VERSION="${KNOT_VERSION:-3ff418dad639a9755b9ef7509ed948578d89be8b}"
# PDS droplet — knot co-hosts herePDS_DEPLOY_DIR="$SCRIPT_DIR/../../pds/deployment/digitalocean"DROPLET_IP_FILE="$PDS_DEPLOY_DIR/.droplet_ip"
info() { echo -e "${BLUE}i${NC} $1"; }success() { echo -e "${GREEN}✓${NC} $1"; }warning() { echo -e "${YELLOW}!${NC} $1"; }error() { echo -e "${RED}x${NC} $1"; exit 1; }
get_droplet_ip() { if [ -f "$DROPLET_IP_FILE" ]; then DROPLET_IP=$(cat "$DROPLET_IP_FILE") elif [ -n "${DROPLET_IP:-}" ]; then : # already set via env else error "Droplet IP not found. Set DROPLET_IP or ensure $DROPLET_IP_FILE exists." fi info "Target: $DROPLET_IP ($KNOT_DOMAIN)"}
get_ssh_key() { # Try PDS key first, then default if [ -f ~/.ssh/aesthetic_pds ]; then SSH_KEY=~/.ssh/aesthetic_pds elif [ -f ~/.ssh/id_ed25519 ]; then SSH_KEY=~/.ssh/id_ed25519 else error "No SSH key found. Expected ~/.ssh/aesthetic_pds or ~/.ssh/id_ed25519" fi info "SSH key: $SSH_KEY"}
get_owner_did() { if [ -n "${KNOT_OWNER_DID:-}" ]; then info "Owner DID: $KNOT_OWNER_DID" return fi
# Try to load from vault if [ -f "$VAULT_DIR/at/knot.env" ]; then source "$VAULT_DIR/at/knot.env" if [ -n "${KNOT_OWNER_DID:-}" ]; then info "Owner DID (from vault): $KNOT_OWNER_DID" return fi fi
# Default: @aesthetic.computer DID (jeffrey) KNOT_OWNER_DID="did:plc:k3k3wknzkcnekbnyde4dbatz" info "Owner DID (aesthetic.computer): $KNOT_OWNER_DID"}
wait_for_ssh() { info "Waiting for SSH..." for i in {1..20}; do if ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -i "$SSH_KEY" "root@$DROPLET_IP" "echo ok" &>/dev/null; then success "SSH ready" return fi sleep 3 done error "SSH timeout"}
remote() { ssh -i "$SSH_KEY" "root@$DROPLET_IP" "$@"}
remote_script() { ssh -i "$SSH_KEY" "root@$DROPLET_IP" 'bash -s' < "$1"}
install_go() { info "Checking Go on remote..." if remote "go version" &>/dev/null; then success "Go already installed" return fi
info "Installing Go..." remote << 'ENDSSH'set -euo pipefailcd /tmpcurl -sLO https://go.dev/dl/go1.24.1.linux-amd64.tar.gzrm -rf /usr/local/gotar -C /usr/local -xzf go1.24.1.linux-amd64.tar.gzrm go1.24.1.linux-amd64.tar.gz
# Add to PATH for all usersecho 'export PATH=$PATH:/usr/local/go/bin' > /etc/profile.d/go.shexport PATH=$PATH:/usr/local/go/bingo versionENDSSH success "Go installed"}
install_build_deps() { info "Installing build dependencies..." remote << 'ENDSSH'set -euo pipefailapt-get update -qqapt-get install -y -qq gcc git make sqlite3 > /dev/nullENDSSH success "Build dependencies installed"}
create_git_user() { info "Creating git user..." if remote "id git" &>/dev/null; then success "git user already exists" return fi
remote << 'ENDSSH'set -euo pipefailadduser --disabled-password --gecos "" gitmkdir -p /home/git/.sshchown -R git:git /home/gitENDSSH success "git user created"}
build_knot() { info "Building knot binary (version: ${KNOT_VERSION:-master})..." remote KNOT_VERSION="$KNOT_VERSION" bash -s << 'ENDSSH'set -euo pipefailexport PATH=$PATH:/usr/local/go/bin
cd /tmprm -rf tangled-core
# Clone from Tangled's own hostinggit clone https://tangled.org/@tangled.org/core tangled-corecd tangled-core
if [ -n "${KNOT_VERSION:-}" ]; then git fetch --tags git checkout "$KNOT_VERSION"fi
CGO_ENABLED=1 go build -o knot ./cmd/knot
# Preserve previous binary for rollbackif [ -f /usr/local/bin/knot ]; then cp -a /usr/local/bin/knot "/usr/local/bin/knot.prev.$(date -u +%Y%m%d-%H%M%S)"fi
install -o root -g root -m 0755 knot /usr/local/bin/knot
rm -rf /tmp/tangled-core
/usr/local/bin/knot --help || trueENDSSH success "Knot binary built and installed"}
configure_ssh_knot() { info "Configuring SSH for knot..." remote << 'ENDSSH'set -euo pipefail
# Configure SSH AuthorizedKeysCommand for git usercat > /etc/ssh/sshd_config.d/tangled-knot.conf << 'SSHEOF'Match User git AuthorizedKeysCommand /usr/local/bin/knot keys -o authorized-keys AuthorizedKeysCommandUser nobodySSHEOF
# Reload SSHsystemctl reload ssh || systemctl reload sshd
echo "SSH configured for knot"ENDSSH success "SSH configured for git user"}
deploy_knot_env() { info "Deploying knot environment..." remote "cat > /home/git/.knot.env" << EOFKNOT_REPO_SCAN_PATH=/home/git/repositoriesKNOT_SERVER_HOSTNAME=$KNOT_DOMAINAPPVIEW_ENDPOINT=$APPVIEW_ENDPOINTKNOT_SERVER_OWNER=$KNOT_OWNER_DIDKNOT_SERVER_INTERNAL_LISTEN_ADDR=127.0.0.1:$KNOT_INTERNAL_PORTKNOT_SERVER_LISTEN_ADDR=127.0.0.1:$KNOT_PUBLIC_PORTKNOT_SERVER_DB_PATH=/home/git/database/knotserver.dbEOF
remote << 'ENDSSH'set -euo pipefailchown git:git /home/git/.knot.envchmod 600 /home/git/.knot.envmkdir -p /home/git/repositories /home/git/database /home/git/logchown -R git:git /home/git/repositories /home/git/database /home/git/log
# Optional MOTDprintf "aesthetic computer knot\n" > /home/git/motdchown git:git /home/git/motdENDSSH success "Knot environment deployed"}
deploy_systemd_service() { info "Deploying systemd service..." cat "$INFRA_DIR/knotserver.service" | remote "cat > /etc/systemd/system/knotserver.service" remote << 'ENDSSH'set -euo pipefailsystemctl daemon-reloadsystemctl enable knotserversystemctl start knotserversleep 2systemctl status knotserver --no-pager || trueENDSSH success "Knot service running"}
configure_caddy() { info "Configuring Caddy reverse proxy..."
# Check if Caddy is managing PDS if remote "systemctl is-active caddy" &>/dev/null; then info "Caddy already running (PDS), adding knot block..."
# Append knot config to existing Caddyfile remote "cat >> /etc/caddy/Caddyfile" << EOF
# Tangled Knot Server$KNOT_DOMAIN { reverse_proxy localhost:$KNOT_PUBLIC_PORT
@websocket { header Connection *Upgrade* header Upgrade websocket } reverse_proxy @websocket localhost:$KNOT_PUBLIC_PORT}EOF remote "systemctl reload caddy" else # PDS might use its own TLS (common with bluesky PDS installer) # Install Caddy for knot only info "Installing Caddy..." remote << 'ENDSSH'set -euo pipefailapt-get install -y -qq debian-keyring debian-archive-keyring apt-transport-https curl > /dev/nullcurl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg 2>/dev/nullcurl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list > /dev/nullapt-get update -qqapt-get install -y -qq caddy > /dev/nullENDSSH
cat "$INFRA_DIR/Caddyfile" | remote "cat > /etc/caddy/Caddyfile" remote "systemctl enable caddy && systemctl restart caddy" fi
success "Caddy configured for $KNOT_DOMAIN"}
configure_dns() { info "Configuring DNS via Cloudflare..."
# Write droplet IP for the DNS script echo "$DROPLET_IP" > "$SCRIPT_DIR/.droplet_ip"
node "$SCRIPT_DIR/configure-dns.mjs" || error "DNS configuration failed"
success "DNS configured"}
verify_deployment() { info "Verifying deployment..." echo ""
# Check systemd if remote "systemctl is-active knotserver" &>/dev/null; then success "knotserver service: active" else warning "knotserver service: not running" remote "journalctl -u knotserver --no-pager -n 20" || true fi
# Check local port if remote "curl -sf http://localhost:$KNOT_PUBLIC_PORT/ > /dev/null 2>&1"; then success "Knot responding on localhost:$KNOT_PUBLIC_PORT" else warning "Knot not responding on localhost:$KNOT_PUBLIC_PORT yet" fi
echo "" info "Next steps:" echo " 1. Verify DNS: dig $KNOT_DOMAIN" echo " 2. Check HTTPS: curl https://$KNOT_DOMAIN/" echo " 3. Register knot at https://tangled.org/settings/knots" echo " 4. Add SSH key at https://tangled.org/settings/keys" echo " 5. Push repos: git remote add tangled git@$KNOT_DOMAIN:aesthetic.computer/core" echo ""}
main() { echo "" echo "╔════════════════════════════════════════════════════════╗" echo "║ Aesthetic Computer Knot Deployment (Tangled) ║" echo "╚════════════════════════════════════════════════════════╝" echo ""
get_droplet_ip get_ssh_key get_owner_did echo ""
if [[ "${AUTO_CONFIRM:-no}" != "yes" ]]; then read -p "Deploy knot to $DROPLET_IP as $KNOT_DOMAIN? (y/N) " -n 1 -r echo "" if [[ ! $REPLY =~ ^[Yy]$ ]]; then error "Cancelled" fi fi
echo "" wait_for_ssh install_build_deps install_go create_git_user build_knot configure_ssh_knot deploy_knot_env deploy_systemd_service configure_dns configure_caddy echo "" verify_deployment
success "Knot deployment complete!"}
if [ $# -gt 0 ]; then "$@"else mainfi