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 ""