Spaces:
Build error
Build error
File size: 9,268 Bytes
d9494a5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 | #!/bin/bash
# =============================================================================
# Twenty CRM — Development Environment Setup
# =============================================================================
# Single entry point for setting up a dev environment. Idempotent.
#
# What it does:
# 1. Starts Postgres + Redis (local services or Docker, auto-detected)
# 2. Creates 'default' and 'test' databases
# 3. Copies .env.example -> .env for front and server
# 4. Initializes database schema (runs migrations) when not already present
#
# Usage (from repo root):
# bash packages/twenty-utils/setup-dev-env.sh # start + configure
# bash packages/twenty-utils/setup-dev-env.sh --down # stop services
# bash packages/twenty-utils/setup-dev-env.sh --reset # wipe data + restart
# bash packages/twenty-utils/setup-dev-env.sh --docker # force Docker mode
# =============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
COMPOSE_FILE="$REPO_ROOT/packages/twenty-docker/docker-compose.dev.yml"
info() { echo "=> $*"; }
ok() { echo " done: $*"; }
fail() { echo " FAIL: $*" >&2; }
# --------------- detection helpers ---------------
has_local_pg() {
command -v pg_ctlcluster &>/dev/null && pg_lsclusters 2>/dev/null | grep -q "16"
}
has_local_redis() {
command -v redis-server &>/dev/null
}
can_use_docker() {
docker compose version &>/dev/null 2>&1
}
pg_is_up() {
if command -v pg_isready &>/dev/null; then
pg_isready -h localhost -p 5432 -U postgres -q 2>/dev/null
elif command -v psql &>/dev/null; then
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -c "SELECT 1" &>/dev/null
elif can_use_docker && docker compose -f "$COMPOSE_FILE" ps --quiet db 2>/dev/null | grep -q .; then
docker compose -f "$COMPOSE_FILE" exec -T db pg_isready -U postgres -q 2>/dev/null
else
return 1
fi
}
redis_is_up() {
if command -v redis-cli &>/dev/null; then
redis-cli -h localhost -p 6379 ping 2>/dev/null | grep -q PONG
elif can_use_docker && docker compose -f "$COMPOSE_FILE" ps --quiet redis 2>/dev/null | grep -q .; then
docker compose -f "$COMPOSE_FILE" exec -T redis redis-cli ping 2>/dev/null | grep -q PONG
else
# Portable fallback using bash /dev/tcp (no nc -q dependency)
timeout 2 bash -c 'exec 3<>/dev/tcp/localhost/6379; echo PING >&3; read -r reply <&3; exec 3>&-; echo "$reply"' 2>/dev/null | grep -q PONG
fi
}
wait_for_pg() {
local retries=30
while ! pg_is_up; do
retries=$((retries - 1))
if [ "$retries" -le 0 ]; then fail "PostgreSQL did not start in time"; exit 1; fi
sleep 1
done
}
wait_for_redis() {
local retries=30
while ! redis_is_up; do
retries=$((retries - 1))
if [ "$retries" -le 0 ]; then fail "Redis did not start in time"; exit 1; fi
sleep 1
done
}
schema_exists() {
if command -v psql &>/dev/null; then
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d default -t -c "SELECT 1 FROM pg_catalog.pg_namespace WHERE nspname = 'core'" 2>/dev/null | grep -q 1
elif can_use_docker && docker compose -f "$COMPOSE_FILE" ps --quiet db 2>/dev/null | grep -q .; then
docker compose -f "$COMPOSE_FILE" exec -T db psql -U postgres -d default -t -c "SELECT 1 FROM pg_catalog.pg_namespace WHERE nspname = 'core'" 2>/dev/null | grep -q 1
else
return 1
fi
}
# --------------- parse flags ---------------
USE_DOCKER=false
ACTION="up"
while [ $# -gt 0 ]; do
case "$1" in
--docker) USE_DOCKER=true ;;
--down) ACTION="down" ;;
--reset) ACTION="reset" ;;
*) echo "Unknown flag: $1"; exit 1 ;;
esac
shift
done
# --------------- stop ---------------
stop_docker() {
if can_use_docker && docker compose -f "$COMPOSE_FILE" ps -a --quiet 2>/dev/null | grep -q .; then
docker compose -f "$COMPOSE_FILE" down "$@"
fi
}
stop_local() {
if has_local_pg; then sudo pg_ctlcluster 16 main stop 2>/dev/null || true; fi
if has_local_redis && pgrep -x redis-server &>/dev/null; then
sudo service redis-server stop 2>/dev/null || true
fi
}
stop_services() {
if [ "$USE_DOCKER" = true ]; then
stop_docker "$@"
else
stop_docker "$@"
stop_local
fi
}
if [ "$ACTION" = "down" ]; then
info "Stopping dev services..."
stop_services
ok "Services stopped"
exit 0
fi
if [ "$ACTION" = "reset" ]; then
info "Resetting dev services (wiping data)..."
# Wipe local Redis data while it's still running
if [ "$USE_DOCKER" = false ] && has_local_redis && pgrep -x redis-server &>/dev/null; then
info "Flushing local Redis data..."
redis-cli flushall 2>/dev/null || true
fi
# Wipe local PostgreSQL data while it's still running
if [ "$USE_DOCKER" = false ] && has_local_pg; then
info "Dropping local databases..."
sudo pg_ctlcluster 16 main start 2>/dev/null || true
wait_for_pg
sudo -u postgres psql -c 'DROP DATABASE IF EXISTS "default";' 2>/dev/null || true
sudo -u postgres psql -c 'DROP DATABASE IF EXISTS "test";' 2>/dev/null || true
fi
# Stop Docker with -v to remove volumes
stop_docker -v 2>/dev/null || stop_docker
# Stop local services
if [ "$USE_DOCKER" = false ]; then
stop_local
fi
fi
# =============================================================================
# 1. Start services (auto-detect: local > Docker)
# =============================================================================
start_pg() {
if pg_is_up; then
ok "PostgreSQL already running"
return
fi
if [ "$USE_DOCKER" = false ] && has_local_pg; then
info "Starting local PostgreSQL..."
sudo pg_ctlcluster 16 main start
wait_for_pg
sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'postgres';" 2>/dev/null || true
elif can_use_docker; then
info "Starting PostgreSQL via Docker..."
docker compose -f "$COMPOSE_FILE" up -d db
wait_for_pg
else
fail "No PostgreSQL available. Install PostgreSQL 16 or Docker."
exit 1
fi
}
start_redis() {
if redis_is_up; then
ok "Redis already running"
return
fi
if [ "$USE_DOCKER" = false ] && has_local_redis; then
info "Starting local Redis..."
sudo service redis-server start 2>/dev/null || redis-server --daemonize yes 2>/dev/null || true
wait_for_redis
elif can_use_docker; then
info "Starting Redis via Docker..."
docker compose -f "$COMPOSE_FILE" up -d redis
wait_for_redis
else
fail "No Redis available. Install Redis or Docker."
exit 1
fi
}
if [ "$USE_DOCKER" = true ]; then
info "Starting services via Docker Compose..."
docker compose -f "$COMPOSE_FILE" up -d
wait_for_pg
wait_for_redis
else
start_pg
start_redis
fi
ok "PostgreSQL on localhost:5432"
ok "Redis on localhost:6379"
# =============================================================================
# 2. Create databases
# =============================================================================
info "Creating databases..."
run_psql() {
if command -v psql &>/dev/null; then
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c "$1" 2>/dev/null || true
elif can_use_docker && docker compose -f "$COMPOSE_FILE" ps --quiet db 2>/dev/null | grep -q .; then
docker compose -f "$COMPOSE_FILE" exec -T db psql -U postgres -d postgres -c "$1" 2>/dev/null || true
else
fail "No psql client available and no Docker db container running"
return 1
fi
}
run_psql 'CREATE DATABASE "default";'
run_psql 'CREATE DATABASE "test";'
ok "Databases 'default' and 'test' ready"
# =============================================================================
# 3. Environment files (via Nx when available, fallback to cp)
# =============================================================================
info "Setting up .env files..."
cd "$REPO_ROOT"
if command -v npx &>/dev/null && [ -d node_modules ]; then
npx nx reset:env twenty-front
npx nx reset:env twenty-server
else
for pkg in twenty-front twenty-server; do
src="packages/$pkg/.env.example"
dst="packages/$pkg/.env"
if [ -f "$src" ] && [ ! -f "$dst" ]; then
cp "$src" "$dst"
ok "$pkg/.env created"
fi
done
fi
# =============================================================================
# 4. Initialize database schema
# =============================================================================
if command -v npx &>/dev/null && [ -d node_modules ]; then
if schema_exists; then
ok "Database schema already initialized"
else
info "Initializing database schema (running migrations)..."
if npx nx database:init twenty-server; then
ok "Database schema initialized"
else
fail "Database schema initialization failed"
exit 1
fi
fi
else
info "Run 'npx nx database:init twenty-server' to initialize the schema"
fi
# =============================================================================
echo ""
echo "Dev environment ready."
echo ""
echo " yarn start # start everything"
echo " npx nx start twenty-front # frontend -> http://localhost:3001"
echo " npx nx start twenty-server # backend -> http://localhost:3000"
echo ""
|