#!/usr/bin/env bash # Everything is served from the Gleam server on :8080 so the OAuth origin stays # consistent. Browse 127.0.0.1, NOT localhost: the atproto localhost client # redirects to the loopback IP and the session cookie is host-specific. set -uo pipefail cd "$(dirname "${BASH_SOURCE[0]}")/.." if [ -f .env ]; then set -a . ./.env set +a fi log() { printf '\033[36m[dev]\033[0m %s\n' "$*"; } # Stable secret so sessions survive the frequent restarts this loop does. export SECRET_KEY_BASE="${SECRET_KEY_BASE:-dev-only-change-me-to-a-long-random-string}" # Required, base64, 32 bytes. export STORE_KEY="${STORE_KEY:-ZGV2LW9ubHktMzItYnl0ZS1zdG9yZS1rZXktISEhISE=}" # BASE_URL left unset => http://localhost:8080 => OAuth redirect to 127.0.0.1:8080. free_8080() { # gleam run spawns a BEAM child, so kill by port rather than by pid to be sure. local pids pids="$(lsof -ti tcp:8080 -sTCP:LISTEN 2>/dev/null || true)" [ -n "$pids" ] && kill $pids 2>/dev/null || true for _ in $(seq 1 25); do lsof -ti tcp:8080 -sTCP:LISTEN >/dev/null 2>&1 || break sleep 0.2 done } build_web() { log "building web bundle" ( cd web && gleam run -m lustre/dev build crate_web ) || { log "web build failed" return 1 } mkdir -p server/priv/static cp -r web/priv/static/. server/priv/static/ cp web/dist/crate_web.js server/priv/static/crate_web.js make css } start_server() { log "starting server -> http://127.0.0.1:8080" ( cd server && exec gleam run ) & } restart_server() { free_8080 start_server } cleanup() { log "shutting down" free_8080 exit 0 } trap cleanup INT TERM log "starting postgres (docker); stopping any dockerized server on :8080" docker compose up -d db docker compose stop server >/dev/null 2>&1 || true free_8080 # A compose network with no host route still reports a real container IP, which # then times out with no hint why, so probe rather than trust the first answer. # -G bounds the connect on macOS (-w alone hangs); OpenBSD nc has no -G. if nc -G 1 -z 127.0.0.1 1 2>&1 | grep -q 'invalid option'; then nc_timeout_flags="-w 2" else nc_timeout_flags="-G 2 -w 2" fi db_reachable() { nc -z $nc_timeout_flags "$1" 5432 >/dev/null 2>&1; } if [ -z "${DATABASE_URL:-}" ]; then db_cid="$(docker compose ps -q db 2>/dev/null | head -n1)" db_name="$(docker inspect -f '{{.Name}}' "$db_cid" 2>/dev/null | sed 's|^/||')" db_hosts="" [ -n "$db_name" ] && db_hosts="${db_name}.orb.local" for ip in $(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}' "$db_cid" 2>/dev/null); do db_hosts="${db_hosts:+$db_hosts }$ip" done db_host="" if ! command -v nc >/dev/null 2>&1; then # Without a prober every candidate looks dead, which reads as "no container". log "nc not found, cannot probe the db; set DATABASE_URL explicitly" else for candidate in $db_hosts; do if db_reachable "$candidate"; then db_host="$candidate" break fi done fi if [ -n "$db_host" ]; then export DATABASE_URL="postgres://atrecord:atrecord@${db_host}:5432/atrecord" log "postgres at ${db_host}:5432" else log "no reachable db host among: ${db_hosts:-(none found)}" log "server will use in-memory sessions; set DATABASE_URL to override" fi fi make gen make vendor build_web start_server SENT="$(mktemp -d)" touch "$SENT/web" "$SENT/server" "$SENT/lex" changed() { # local sentinel="$1" shift if find "$@" -type f -newer "$sentinel" -print -quit 2>/dev/null | grep -q .; then touch "$sentinel" return 0 fi return 1 } log "watching lexicons/, server/src, shared/src, web/src, web/css, web/priv/static (Ctrl-C to stop)" while true; do if changed "$SENT/lex" lexicons; then log "lexicons changed -> codegen + full rebuild" make gen && build_web && restart_server elif changed "$SENT/server" server/src shared/src; then log "server/shared changed -> rebuild + restart" build_web && restart_server elif changed "$SENT/web" web/src web/css web/priv/static; then log "web changed -> rebuild bundle (refresh the browser)" build_web fi sleep 1 done