-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstack.sh
More file actions
executable file
·240 lines (211 loc) · 8.98 KB
/
Copy pathstack.sh
File metadata and controls
executable file
·240 lines (211 loc) · 8.98 KB
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
#!/usr/bin/env bash
# FeatureApp — orquestador del entorno completo (backend + frontend).
# Envuelve los dos `docker compose` independientes que viven en
# backend/ y frontend/ con un único punto de entrada.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BACKEND_DIR="$ROOT/backend"
FRONTEND_DIR="$ROOT/frontend"
BACKEND_ENV_FILE=".env.docker"
BACKEND_COMPOSE=(-f docker-compose.dev.yml)
FRONTEND_ENV_FILE="docker/.env"
# Dev usa SOLO el archivo dev (autocontenido, como el backend). Prod usa el base
# (frontend + nginx). No se mergean: evita que config de prod se filtre a dev.
FRONTEND_COMPOSE_BASE=(-f docker/compose/docker-compose.yml)
FRONTEND_COMPOSE_DEV=(-f docker/compose/docker-compose.dev.yml)
TARGET="all" # all | backend | frontend
MODE="dev" # dev | prod (solo aplica al frontend)
PROFILES=() # backend: workers / dev-assets
DETACH="-d"
EXTRA=()
color() { printf '\033[1;36m==> %s\033[0m\n' "$*"; }
warn() { printf '\033[1;33m!! %s\033[0m\n' "$*" >&2; }
die() { printf '\033[1;31mxx %s\033[0m\n' "$*" >&2; exit 1; }
usage() {
cat <<EOF
FeatureApp stack — backend (Laravel) + frontend (Next.js)
Uso:
./stack.sh <comando> [opciones] [-- args extra para docker compose]
Comandos:
build Construye las imágenes
up Levanta los contenedores
down Baja y elimina los contenedores
restart Reinicia (recarga) los contenedores ya creados
rebuild down + build + up (reset completo, preserva volúmenes)
logs Sigue los logs (-f)
ps Estado de los contenedores
shell <svc> Abre sh dentro de un servicio (requiere --backend o --frontend)
help Muestra esta ayuda
Opciones:
--backend Solo backend
--frontend Solo frontend
--prod Frontend en modo producción (default: dev con hot-reload)
--workers Backend: agrega perfil workers (queue + scheduler)
--dev-assets Backend: agrega perfil dev-assets (Vite)
--no-detach up sin -d (foreground)
--fresh En 'down': borra volúmenes (-v). ¡Pierde datos de Postgres!
-h | --help Muestra esta ayuda
Ejemplos:
./stack.sh up # backend + frontend (dev), detached
./stack.sh up --frontend # solo frontend dev
./stack.sh up --backend --workers # backend + queue + scheduler
./stack.sh build --prod # build de producción del frontend
./stack.sh restart --backend
./stack.sh logs --frontend
./stack.sh down --fresh # baja todo y borra volúmenes
./stack.sh shell app --backend # sh dentro del contenedor laravel
./stack.sh up -- --remove-orphans # passthrough a docker compose
EOF
}
# --- argument parsing ---------------------------------------------------------
[[ $# -eq 0 ]] && { usage; exit 0; }
CMD="$1"; shift || true
while [[ $# -gt 0 ]]; do
case "$1" in
--backend) TARGET="backend"; shift ;;
--frontend) TARGET="frontend"; shift ;;
--prod) MODE="prod"; shift ;;
--workers) PROFILES+=(--profile workers); shift ;;
--dev-assets) PROFILES+=(--profile dev-assets); shift ;;
--no-detach) DETACH=""; shift ;;
--fresh) FRESH="-v"; shift ;;
-h|--help) usage; exit 0 ;;
--) shift; EXTRA+=("$@"); break ;;
*) EXTRA+=("$1"); shift ;;
esac
done
FRESH="${FRESH:-}"
# --- helpers ------------------------------------------------------------------
backend_compose() {
( cd "$BACKEND_DIR" && docker compose --env-file "$BACKEND_ENV_FILE" "${BACKEND_COMPOSE[@]}" "${PROFILES[@]}" "$@" )
}
frontend_compose() {
local files=("${FRONTEND_COMPOSE_BASE[@]}")
[[ "$MODE" == "dev" ]] && files=("${FRONTEND_COMPOSE_DEV[@]}")
( cd "$FRONTEND_DIR" && docker compose --env-file "$FRONTEND_ENV_FILE" "${files[@]}" "$@" )
}
ensure_backend_env() {
# Solo copiamos .env desde .env.example si Laravel ya está instalado (existe el
# example). Si app-laravel/ está vacío, el bootstrap (install-laravel.sh) crea
# y cablea el .env; no es un error todavía.
if [[ ! -f "$BACKEND_DIR/app-laravel/.env" && -f "$BACKEND_DIR/app-laravel/.env.example" ]]; then
warn "backend/app-laravel/.env no existe — copiando desde .env.example"
cp "$BACKEND_DIR/app-laravel/.env.example" "$BACKEND_DIR/app-laravel/.env"
fi
if [[ ! -f "$BACKEND_DIR/$BACKEND_ENV_FILE" ]]; then
die "backend/$BACKEND_ENV_FILE no existe. Es requerido para los puertos/UID del compose (USER_ID, APP_PORT, FORWARD_*_PORT, DB_*)."
fi
}
ensure_frontend_env() {
if [[ ! -f "$FRONTEND_DIR/$FRONTEND_ENV_FILE" ]]; then
die "frontend/docker/.env no existe. Copialo de docker/.env.example y configurá NEXT_PUBLIC_API_URL."
fi
}
# Tras buildear la imagen del backend, instala Laravel en app-laravel/ si el
# directorio está vacío. Corre el instalador (baked en la imagen) en un
# contenedor efímero que escribe en el bind-mount del host, así la app queda
# instalada al hacer `build` (no recién en el primer `up`).
bootstrap_backend_app() {
if [[ -f "$BACKEND_DIR/app-laravel/artisan" || -f "$BACKEND_DIR/app-laravel/composer.json" ]]; then
return 0 # ya instalado
fi
color "[backend] app-laravel/ vacío — instalando Laravel"
backend_compose run --rm --no-deps --entrypoint /usr/local/bin/install-laravel app
}
# Instala Next.js en frontend/app-next/ si no hay app (falta package.json).
# Corre el instalador en un contenedor node:22-alpine efímero con el script
# montado, escribiendo en el bind-mount del host. A diferencia del backend, no
# requiere imagen horneada (node+pnpm son de stock).
#
# GARANTÍA de destino: el bind-mount usa la ruta absoluta $APP_DIR
# (= frontend/app-next) montada en /app, el instalador hace `cd /app`, y al
# terminar se verifica que package.json quedó EXACTAMENTE en $APP_DIR.
bootstrap_frontend_app() {
local app_dir="$FRONTEND_DIR/app-next"
if [[ -f "$app_dir/package.json" ]]; then
return 0 # ya instalado
fi
# Crear la ruta destino antes del run: evita que el daemon la cree root-owned
# y soporta un clon fresco donde frontend/app-next/ todavía no existe.
mkdir -p "$app_dir"
color "[frontend] Next.js ausente — instalando (create-next-app) en $app_dir"
docker run --rm \
-v "$app_dir":/app \
-v "$FRONTEND_DIR/docker/frontend/install-next.sh":/usr/local/bin/install-next:ro \
-w /app \
--env-file "$FRONTEND_DIR/$FRONTEND_ENV_FILE" \
-e HOST_UID="$(id -u)" -e HOST_GID="$(id -g)" \
node:22-alpine /usr/local/bin/install-next
# Garantía: la instalación debe haber aterrizado en frontend/app-next/.
[[ -f "$app_dir/package.json" ]] \
|| die "el bootstrap no dejó package.json en $app_dir — Next.js NO quedó instalado en frontend/app-next/"
color "[frontend] Next.js instalado en $app_dir"
}
run_on_targets() {
local action="$1"; shift
case "$TARGET" in
backend)
ensure_backend_env
color "[backend] $action"
"$@" backend_compose
;;
frontend)
ensure_frontend_env
color "[frontend] $action"
"$@" frontend_compose
;;
all)
ensure_backend_env
ensure_frontend_env
color "[backend] $action"
"$@" backend_compose
color "[frontend] $action"
"$@" frontend_compose
;;
esac
}
# --- command implementations --------------------------------------------------
do_build() {
[[ "$1" == "frontend_compose" ]] && bootstrap_frontend_app # antes del build (COPY package.json)
"$1" build "${EXTRA[@]}"
[[ "$1" == "backend_compose" ]] && bootstrap_backend_app # después del build (instalador horneado)
}
do_up() {
[[ "$1" == "frontend_compose" ]] && bootstrap_frontend_app # up sin build previo igual garantiza la app
"$1" up $DETACH "${EXTRA[@]}"
}
do_down() { "$1" down $FRESH "${EXTRA[@]}"; }
do_restart() { "$1" restart "${EXTRA[@]}"; }
do_logs() { "$1" logs -f --tail=100 "${EXTRA[@]}"; }
do_ps() { "$1" ps "${EXTRA[@]}"; }
do_rebuild() {
local fn="$1"
"$fn" down "${EXTRA[@]}"
[[ "$fn" == "frontend_compose" ]] && bootstrap_frontend_app # antes del build
"$fn" build
[[ "$fn" == "backend_compose" ]] && bootstrap_backend_app # después del build
"$fn" up $DETACH
}
do_shell() {
[[ ${#EXTRA[@]} -ge 1 ]] || die "shell requiere el nombre del servicio (ej: shell app --backend)"
[[ "$TARGET" == "all" ]] && die "shell requiere --backend o --frontend"
local svc="${EXTRA[0]}"
if [[ "$TARGET" == "backend" ]]; then
backend_compose exec "$svc" sh
else
frontend_compose exec "$svc" sh
fi
}
# --- dispatcher ---------------------------------------------------------------
case "$CMD" in
build) run_on_targets "build" do_build ;;
up) run_on_targets "up" do_up ;;
down) run_on_targets "down" do_down ;;
restart) run_on_targets "restart" do_restart ;;
rebuild) run_on_targets "rebuild" do_rebuild ;;
logs) run_on_targets "logs" do_logs ;;
ps|status) run_on_targets "ps" do_ps ;;
shell) do_shell ;;
help|-h|--help) usage ;;
*) die "Comando desconocido: $CMD (probá './stack.sh help')" ;;
esac