@oktis-works/cms 0.1.13 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets.d.ts +94 -0
- package/dist/assets.d.ts.map +1 -0
- package/dist/assets.js +671 -0
- package/dist/assets.js.map +1 -0
- package/dist/bluegreen.d.ts +121 -0
- package/dist/bluegreen.d.ts.map +1 -0
- package/dist/bluegreen.js +393 -0
- package/dist/bluegreen.js.map +1 -0
- package/dist/commands.d.ts +6 -0
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +116 -61
- package/dist/commands.js.map +1 -1
- package/dist/config-wizard.d.ts +39 -0
- package/dist/config-wizard.d.ts.map +1 -0
- package/dist/config-wizard.js +308 -0
- package/dist/config-wizard.js.map +1 -0
- package/dist/docker.d.ts +148 -0
- package/dist/docker.d.ts.map +1 -0
- package/dist/docker.js +324 -0
- package/dist/docker.js.map +1 -0
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +51 -0
- package/dist/doctor.js.map +1 -1
- package/dist/env-file.d.ts +45 -0
- package/dist/env-file.d.ts.map +1 -0
- package/dist/env-file.js +200 -0
- package/dist/env-file.js.map +1 -0
- package/dist/env-schema.d.ts +28 -0
- package/dist/env-schema.d.ts.map +1 -0
- package/dist/env-schema.js +497 -0
- package/dist/env-schema.js.map +1 -0
- package/dist/guards.d.ts +40 -0
- package/dist/guards.d.ts.map +1 -0
- package/dist/guards.js +159 -0
- package/dist/guards.js.map +1 -0
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +12 -3
- package/dist/parser.js.map +1 -1
- package/dist/project-config.js +2 -2
- package/dist/prompt.d.ts +81 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +379 -0
- package/dist/prompt.js.map +1 -0
- package/dist/redeploy.d.ts +109 -0
- package/dist/redeploy.d.ts.map +1 -0
- package/dist/redeploy.js +363 -0
- package/dist/redeploy.js.map +1 -0
- package/dist/scaffold-docs.d.ts +25 -6
- package/dist/scaffold-docs.d.ts.map +1 -1
- package/dist/scaffold-docs.js +711 -33
- package/dist/scaffold-docs.js.map +1 -1
- package/dist/scaffold.d.ts +17 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +77 -14
- package/dist/scaffold.js.map +1 -1
- package/dist/theme-build.d.ts +35 -0
- package/dist/theme-build.d.ts.map +1 -0
- package/dist/theme-build.js +86 -0
- package/dist/theme-build.js.map +1 -0
- package/dist/update.d.ts +51 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +210 -0
- package/dist/update.js.map +1 -0
- package/package.json +7 -7
package/dist/assets.js
ADDED
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
// @oktis-works/cms - Assets canônicos de deploy embutidos na CLI
|
|
2
|
+
//
|
|
3
|
+
// Um projeto gerado pelo `okcms init` não tem (e não pode ter) acesso a este
|
|
4
|
+
// repositório — então os arquivos de infraestrutura viajam DENTRO do pacote
|
|
5
|
+
// npm como strings e são escritos em disco pelo scaffold/update.
|
|
6
|
+
//
|
|
7
|
+
// Estes textos são a fonte canônica. `infrastructure/**` espelha cada um deles
|
|
8
|
+
// e o teste `assets.test.ts` falha se divergirem: sem isso a cópia em
|
|
9
|
+
// infraestrutura vira documentação morta que ninguém atualiza.
|
|
10
|
+
//
|
|
11
|
+
// Escapamento: dentro dos template literals todo `${...}` do shell/compose
|
|
12
|
+
// precisa virar `\${...}` para não virar interpolação do TypeScript.
|
|
13
|
+
/** Espelhos em disco (mantidos em sincronia por teste). */
|
|
14
|
+
export const ASSET_MIRROR = {
|
|
15
|
+
dockerfile: 'infrastructure/docker/Dockerfile',
|
|
16
|
+
entrypoint: 'infrastructure/docker/entrypoint.sh',
|
|
17
|
+
dockerignore: 'infrastructure/docker/.dockerignore',
|
|
18
|
+
composeInfra: 'infrastructure/compose/docker-compose.infra.yml',
|
|
19
|
+
composeDeploy: 'infrastructure/compose/docker-compose.deploy.yml',
|
|
20
|
+
nginxTemplate: 'infrastructure/nginx/templates/default.conf.template',
|
|
21
|
+
};
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Imagem única — 4 entrypoints (api | admin | web | worker)
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
/**
|
|
26
|
+
* Uma imagem para os quatro papéis: mesma camada de dependências em cache,
|
|
27
|
+
* processo escolhido pelo `command` do compose. Quatro Dockerfiles seriam
|
|
28
|
+
* quatro builds idênticos consumindo o mesmo node_modules.
|
|
29
|
+
*/
|
|
30
|
+
export const DOCKERFILE = `# syntax=docker/dockerfile:1
|
|
31
|
+
# @oktis-works - Imagem única do OkCMS (entrypoints: api | admin | web | worker)
|
|
32
|
+
#
|
|
33
|
+
# Gerada pelo scaffold da CLI. Canônica em
|
|
34
|
+
# infrastructure/docker/Dockerfile neste monorepo.
|
|
35
|
+
#
|
|
36
|
+
# SEGURANÇA: a ferramenta de linha de comando NÃO entra nesta imagem. O
|
|
37
|
+
# container expõe apenas os processos da aplicação — sem instalador de
|
|
38
|
+
# pacotes, sem acesso ao socket do Docker, sem capacidade de alterar o
|
|
39
|
+
# próprio estado. Deploy e configuração sempre rodam no host.
|
|
40
|
+
FROM oven/bun:1-alpine AS base
|
|
41
|
+
WORKDIR /app
|
|
42
|
+
ENV NODE_ENV=production
|
|
43
|
+
|
|
44
|
+
# --- dependências -----------------------------------------------------------
|
|
45
|
+
# Manifest + lock primeiro: trocar só o código não invalida este \`install\`.
|
|
46
|
+
FROM base AS deps
|
|
47
|
+
COPY package.json bun.lock* package-lock.json* yarn.lock* pnpm-lock.yaml* ./
|
|
48
|
+
# Fallbacks: lock ausente/divergente (npm/yarn) ou bun antigo sem --production
|
|
49
|
+
RUN bun install --production --frozen-lockfile \\
|
|
50
|
+
|| bun install --production \\
|
|
51
|
+
|| bun install
|
|
52
|
+
|
|
53
|
+
# --- runtime ----------------------------------------------------------------
|
|
54
|
+
FROM base AS runtime
|
|
55
|
+
# 1. código do projeto (.dockerignore remove .env, node_modules e .data)
|
|
56
|
+
COPY . .
|
|
57
|
+
# 2. node_modules limpo do estágio de deps, por cima — nunca o do host
|
|
58
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
59
|
+
|
|
60
|
+
RUN mkdir -p /app/.data/storage /app/.data/cache /app/logs
|
|
61
|
+
|
|
62
|
+
EXPOSE 3000 3001 3011
|
|
63
|
+
|
|
64
|
+
# \`docker compose ... command: api\` escolhe o papel; ver docker/entrypoint.sh
|
|
65
|
+
ENTRYPOINT ["/app/docker/entrypoint.sh"]
|
|
66
|
+
CMD ["api"]
|
|
67
|
+
`;
|
|
68
|
+
/** Dispatcher de papéis. Fora da lista, executa o comando literal. */
|
|
69
|
+
export const ENTRYPOINT_SH = `#!/bin/sh
|
|
70
|
+
# @oktis-works - Entrada única da imagem okcms/app
|
|
71
|
+
#
|
|
72
|
+
# O compose escolhe o papel com \`command: api|web|admin|worker\`. Qualquer
|
|
73
|
+
# outro valor é repassado literalmente (\`docker run okcms/app sh\`), o que
|
|
74
|
+
# mantem o container inspecionável sem reescrever a imagem.
|
|
75
|
+
set -eu
|
|
76
|
+
|
|
77
|
+
api_entry="node_modules/@oktis-works/api/dist/index.js"
|
|
78
|
+
web_entry="node_modules/@oktis-works/web/dist/index.js"
|
|
79
|
+
admin_entry="node_modules/@oktis-works/admin/bin/admin.js"
|
|
80
|
+
worker_entry="node_modules/@oktis-works/worker/dist/index.js"
|
|
81
|
+
|
|
82
|
+
case "\${1:-api}" in
|
|
83
|
+
api)
|
|
84
|
+
shift
|
|
85
|
+
exec bun "$api_entry" "$@"
|
|
86
|
+
;;
|
|
87
|
+
web)
|
|
88
|
+
shift
|
|
89
|
+
exec bun "$web_entry" "$@"
|
|
90
|
+
;;
|
|
91
|
+
admin)
|
|
92
|
+
shift
|
|
93
|
+
exec bun "$admin_entry" "$@"
|
|
94
|
+
;;
|
|
95
|
+
worker)
|
|
96
|
+
shift
|
|
97
|
+
exec bun "$worker_entry" "$@"
|
|
98
|
+
;;
|
|
99
|
+
*)
|
|
100
|
+
exec "$@"
|
|
101
|
+
;;
|
|
102
|
+
esac
|
|
103
|
+
`;
|
|
104
|
+
/**
|
|
105
|
+
* Camada de segurança do build: além do ganho de cache, impede dois acidentes
|
|
106
|
+
* caros — assinar a imagem com o `.env` real e instalar `node_modules` do host
|
|
107
|
+
* (arquitetura diferente / código não auditado).
|
|
108
|
+
*/
|
|
109
|
+
export const DOCKERIGNORE = `# @oktis-works - build context da imagem okcms/app
|
|
110
|
+
# .env NUNCA entra na imagem: ele carrega senha de banco e JWT_SECRET e a
|
|
111
|
+
# imagem é distribuída/copiada. Em runtime ele vem do host via env_file.
|
|
112
|
+
|
|
113
|
+
.env
|
|
114
|
+
.env.*
|
|
115
|
+
!.env.example
|
|
116
|
+
|
|
117
|
+
# dependências do host (a imagem instala as suas)
|
|
118
|
+
node_modules
|
|
119
|
+
**/node_modules
|
|
120
|
+
.tsbuildinfo
|
|
121
|
+
**/*.tsbuildinfo
|
|
122
|
+
# NB: NÃO ignorar dist/ — plugins instalados em plugins/*/dist são runtime.
|
|
123
|
+
|
|
124
|
+
# dados e estado local
|
|
125
|
+
.data
|
|
126
|
+
**/.data
|
|
127
|
+
logs
|
|
128
|
+
**/logs
|
|
129
|
+
*.log
|
|
130
|
+
coverage
|
|
131
|
+
**/coverage
|
|
132
|
+
|
|
133
|
+
# montado em runtime, não copiado
|
|
134
|
+
deploy/
|
|
135
|
+
|
|
136
|
+
# VCS e tooling — não é código de runtime
|
|
137
|
+
.git
|
|
138
|
+
.github
|
|
139
|
+
.gitignore
|
|
140
|
+
.sdd
|
|
141
|
+
.vscode
|
|
142
|
+
.idea
|
|
143
|
+
.claude
|
|
144
|
+
*.md
|
|
145
|
+
!README.md
|
|
146
|
+
|
|
147
|
+
# infraestrutura montada em runtime, não copiada para dentro
|
|
148
|
+
docker-compose*.yml
|
|
149
|
+
deploy/nginx/certs
|
|
150
|
+
`;
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// Infra compartilhada (projeto Docker `okcms`)
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
/**
|
|
155
|
+
* Banco, cache e proxy — únicos e estáveis por toda a vida do site. Moram num
|
|
156
|
+
* projeto PRÓPRIO para que `docker compose -p okcms-blue down` não derrube o
|
|
157
|
+
* banco junto com a lane antiga.
|
|
158
|
+
*/
|
|
159
|
+
export const COMPOSE_INFRA = `# @oktis-works - Infraestrutura compartilhada do deploy em produção
|
|
160
|
+
# Gerada pela CLI. Canônica em infrastructure/compose/docker-compose.infra.yml
|
|
161
|
+
#
|
|
162
|
+
# Projeto Docker PRÓPRIO (okcms): postgres, redis e proxy únicos. As lanes
|
|
163
|
+
# blue/green são projetos separados (okcms-blue / okcms-green) que entram
|
|
164
|
+
# nesta rede como externa — por isso \`down\` numa lane nunca toca no banco.
|
|
165
|
+
#
|
|
166
|
+
# docker compose -p okcms -f docker-compose.infra.yml up -d
|
|
167
|
+
name: okcms
|
|
168
|
+
|
|
169
|
+
services:
|
|
170
|
+
postgres:
|
|
171
|
+
image: postgres:16-alpine
|
|
172
|
+
container_name: okcms-postgres
|
|
173
|
+
restart: unless-stopped
|
|
174
|
+
environment:
|
|
175
|
+
POSTGRES_DB: \${DB_NAME:-okcms}
|
|
176
|
+
POSTGRES_USER: \${DB_USER:-postgres}
|
|
177
|
+
POSTGRES_PASSWORD: \${DB_PASSWORD:?defina DB_PASSWORD no .env}
|
|
178
|
+
volumes:
|
|
179
|
+
- postgres-data:/var/lib/postgresql/data
|
|
180
|
+
ports:
|
|
181
|
+
# Somente loopback: o host consegue rodar \`okcms db:migrate\` e
|
|
182
|
+
# \`db:backup\` sem expor o Postgres para a rede. Em produção gerenciada
|
|
183
|
+
# remova este bloco e aponte DB_HOST/DATABASE_URL para o provedor.
|
|
184
|
+
- "127.0.0.1:\${DB_HOST_PORT:-5432}:5432"
|
|
185
|
+
healthcheck:
|
|
186
|
+
test: ["CMD-SHELL", "pg_isready -U $\${POSTGRES_USER} -d $\${POSTGRES_DB}"]
|
|
187
|
+
interval: 10s
|
|
188
|
+
timeout: 5s
|
|
189
|
+
retries: 10
|
|
190
|
+
labels:
|
|
191
|
+
# classificação lida pelo \`okcms update\`: data = intocável
|
|
192
|
+
okcms.role: data
|
|
193
|
+
networks: [okcms]
|
|
194
|
+
|
|
195
|
+
redis:
|
|
196
|
+
image: redis:7-alpine
|
|
197
|
+
container_name: okcms-redis
|
|
198
|
+
restart: unless-stopped
|
|
199
|
+
environment:
|
|
200
|
+
REDIS_PASSWORD: \${REDIS_PASSWORD:-}
|
|
201
|
+
command:
|
|
202
|
+
- sh
|
|
203
|
+
- -c
|
|
204
|
+
- |
|
|
205
|
+
if [ -n "$\${REDIS_PASSWORD:-}" ]; then
|
|
206
|
+
exec redis-server --appendonly yes --requirepass "$$REDIS_PASSWORD"
|
|
207
|
+
else
|
|
208
|
+
exec redis-server --appendonly yes
|
|
209
|
+
fi
|
|
210
|
+
# Nenhuma porta publicada: só quem está em okcms-net alcança o Redis.
|
|
211
|
+
volumes:
|
|
212
|
+
- redis-data:/data
|
|
213
|
+
healthcheck:
|
|
214
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
215
|
+
interval: 10s
|
|
216
|
+
timeout: 5s
|
|
217
|
+
retries: 10
|
|
218
|
+
labels:
|
|
219
|
+
okcms.role: data
|
|
220
|
+
networks: [okcms]
|
|
221
|
+
|
|
222
|
+
proxy:
|
|
223
|
+
image: nginx:1.27-alpine
|
|
224
|
+
container_name: okcms-proxy
|
|
225
|
+
restart: unless-stopped
|
|
226
|
+
ports:
|
|
227
|
+
- "80:80"
|
|
228
|
+
- "8080:8080"
|
|
229
|
+
environment:
|
|
230
|
+
# entram no envsubst nativo do nginx → gravam conf.d/default.conf
|
|
231
|
+
SERVER_NAME: \${SERVER_NAME:-_}
|
|
232
|
+
ADMIN_SERVER_NAME: \${ADMIN_SERVER_NAME:-admin.localhost}
|
|
233
|
+
volumes:
|
|
234
|
+
- ./deploy/nginx/templates:/etc/nginx/templates:ro
|
|
235
|
+
# conf.d precisa ser gravável: a CLI reescreve 00-upstreams.conf no swap
|
|
236
|
+
- ./deploy/nginx/conf.d:/etc/nginx/conf.d:rw
|
|
237
|
+
labels:
|
|
238
|
+
okcms.role: proxy
|
|
239
|
+
networks: [okcms]
|
|
240
|
+
|
|
241
|
+
networks:
|
|
242
|
+
okcms:
|
|
243
|
+
# nome fixo — as lanes o declaram como external
|
|
244
|
+
name: okcms-net
|
|
245
|
+
|
|
246
|
+
volumes:
|
|
247
|
+
postgres-data:
|
|
248
|
+
name: okcms-postgres-data
|
|
249
|
+
redis-data:
|
|
250
|
+
name: okcms-redis-data
|
|
251
|
+
`;
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
// Lanes blue/green (projetos Docker `okcms-blue` / `okcms-green`)
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
/**
|
|
256
|
+
* Um arquivo, dois projetos Docker. O "lane" é o NOME DO PROJETO — não há
|
|
257
|
+
* serviço repetido por perfil nem perfil a ativar manualmente; basta
|
|
258
|
+
* `-p okcms-blue` ou `-p okcms-green`. Os containers herdam o prefixo
|
|
259
|
+
* `okcms-<lane>-`, que é exatamente o que os upstreams do nginx apontam.
|
|
260
|
+
*/
|
|
261
|
+
export const COMPOSE_DEPLOY = `# @oktis-works - Lanes blue/green do OkCMS
|
|
262
|
+
# Gerada pela CLI. Canônica em infrastructure/compose/docker-compose.deploy.yml
|
|
263
|
+
#
|
|
264
|
+
# Um ARQUIVO, DOIS projetos Docker. O lane é o nome do projeto:
|
|
265
|
+
#
|
|
266
|
+
# docker compose -p okcms-blue -f docker-compose.deploy.yml up -d --build
|
|
267
|
+
# docker compose -p okcms-green -f docker-compose.deploy.yml up -d --build
|
|
268
|
+
#
|
|
269
|
+
# Containers ficam okcms-<lane>-<serviço> — os upstreams do nginx apontam
|
|
270
|
+
# para esses nomes (gerados em deploy/nginx/conf.d/00-upstreams.conf).
|
|
271
|
+
#
|
|
272
|
+
# A rede okcms-net é EXTERNA (criada pelo docker-compose.infra.yml) e os
|
|
273
|
+
# serviços de infra NÃO aparecem aqui de propósito: sem isso, \`down\` de uma
|
|
274
|
+
# lane derrubaria o banco junto com a lane antiga.
|
|
275
|
+
#
|
|
276
|
+
# Papéis (label okcms.role, lida pelo \`okcms update\`):
|
|
277
|
+
# edge api/web/admin → trocados por completo a cada deploy (blue/green)
|
|
278
|
+
# worker fila BullMQ → NUNCA blue/green; drenado com SIGTERM no swap
|
|
279
|
+
# data postgres/redis→ intocáveis; moram no docker-compose.infra.yml
|
|
280
|
+
#
|
|
281
|
+
# NB: o \`down\` de lane derruba CONTAINERS e REDES, mas nunca passa de
|
|
282
|
+
# \`\`\` -v \`\`\`: o volume okcms-storage é compartilhado entre blue e green,
|
|
283
|
+
# é ele que mantém a mídia viva através dos deploys.
|
|
284
|
+
|
|
285
|
+
x-app: &app
|
|
286
|
+
image: okcms/app:\${OKCMS_VERSION:-latest}
|
|
287
|
+
build:
|
|
288
|
+
context: .
|
|
289
|
+
dockerfile: docker/Dockerfile
|
|
290
|
+
env_file:
|
|
291
|
+
# .env primeiro (portas do operador) — o environment abaixo sobrescreve
|
|
292
|
+
- .env
|
|
293
|
+
restart: unless-stopped
|
|
294
|
+
networks: [okcms]
|
|
295
|
+
environment:
|
|
296
|
+
NODE_ENV: production
|
|
297
|
+
DB_HOST: postgres
|
|
298
|
+
REDIS_HOST: redis
|
|
299
|
+
volumes:
|
|
300
|
+
- ./plugins:/app/plugins
|
|
301
|
+
- ./themes:/app/themes
|
|
302
|
+
# mídia compartilhada entre as duas lanes: deploy não perde arquivo
|
|
303
|
+
- storage-data:/app/.data/storage
|
|
304
|
+
- cache-data:/app/.data/cache
|
|
305
|
+
|
|
306
|
+
x-api-health: &api-health
|
|
307
|
+
test:
|
|
308
|
+
- CMD
|
|
309
|
+
- bun
|
|
310
|
+
- -e
|
|
311
|
+
- "fetch('http://127.0.0.1:'+(process.env.PORT||3000)+'/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
|
312
|
+
interval: 10s
|
|
313
|
+
timeout: 5s
|
|
314
|
+
retries: 12
|
|
315
|
+
start_period: 25s
|
|
316
|
+
|
|
317
|
+
x-web-health: &web-health
|
|
318
|
+
test:
|
|
319
|
+
- CMD
|
|
320
|
+
- bun
|
|
321
|
+
- -e
|
|
322
|
+
- "fetch('http://127.0.0.1:'+(process.env.WEB_PORT||3001)+'/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
|
323
|
+
interval: 10s
|
|
324
|
+
timeout: 5s
|
|
325
|
+
retries: 12
|
|
326
|
+
start_period: 25s
|
|
327
|
+
|
|
328
|
+
x-admin-health: &admin-health
|
|
329
|
+
# o admin é Astro standalone: serve / e não tem /health dedicado
|
|
330
|
+
test:
|
|
331
|
+
- CMD
|
|
332
|
+
- bun
|
|
333
|
+
- -e
|
|
334
|
+
- "fetch('http://127.0.0.1:'+(process.env.PORT||3011)+'/').then(r=>process.exit(r.status<500?0:1)).catch(()=>process.exit(1))"
|
|
335
|
+
interval: 10s
|
|
336
|
+
timeout: 5s
|
|
337
|
+
retries: 12
|
|
338
|
+
start_period: 30s
|
|
339
|
+
|
|
340
|
+
services:
|
|
341
|
+
# ---------------------------------------------------------------- api
|
|
342
|
+
api-blue:
|
|
343
|
+
<<: *app
|
|
344
|
+
container_name: okcms-api-blue
|
|
345
|
+
command: ["api"]
|
|
346
|
+
labels:
|
|
347
|
+
okcms.role: edge
|
|
348
|
+
okcms.lane: blue
|
|
349
|
+
okcms.service: api
|
|
350
|
+
healthcheck: *api-health
|
|
351
|
+
|
|
352
|
+
api-green:
|
|
353
|
+
<<: *app
|
|
354
|
+
container_name: okcms-api-green
|
|
355
|
+
command: ["api"]
|
|
356
|
+
labels:
|
|
357
|
+
okcms.role: edge
|
|
358
|
+
okcms.lane: green
|
|
359
|
+
okcms.service: api
|
|
360
|
+
healthcheck: *api-health
|
|
361
|
+
|
|
362
|
+
# ---------------------------------------------------------------- web
|
|
363
|
+
web-blue:
|
|
364
|
+
<<: *app
|
|
365
|
+
container_name: okcms-web-blue
|
|
366
|
+
command: ["web"]
|
|
367
|
+
labels:
|
|
368
|
+
okcms.role: edge
|
|
369
|
+
okcms.lane: blue
|
|
370
|
+
okcms.service: web
|
|
371
|
+
healthcheck: *web-health
|
|
372
|
+
|
|
373
|
+
web-green:
|
|
374
|
+
<<: *app
|
|
375
|
+
container_name: okcms-web-green
|
|
376
|
+
command: ["web"]
|
|
377
|
+
labels:
|
|
378
|
+
okcms.role: edge
|
|
379
|
+
okcms.lane: green
|
|
380
|
+
okcms.service: web
|
|
381
|
+
healthcheck: *web-health
|
|
382
|
+
|
|
383
|
+
# -------------------------------------------------------------- admin
|
|
384
|
+
# admin lê \`PORT\` (astro standalone) e não \`ADMIN_PORT\`: o environment do
|
|
385
|
+
# service substitui o do anchor \`<<\`, então os três comuns são repetidos.
|
|
386
|
+
admin-blue:
|
|
387
|
+
<<: *app
|
|
388
|
+
container_name: okcms-admin-blue
|
|
389
|
+
command: ["admin"]
|
|
390
|
+
environment:
|
|
391
|
+
NODE_ENV: production
|
|
392
|
+
DB_HOST: postgres
|
|
393
|
+
REDIS_HOST: redis
|
|
394
|
+
PORT: \${ADMIN_PORT:-3011}
|
|
395
|
+
labels:
|
|
396
|
+
okcms.role: edge
|
|
397
|
+
okcms.lane: blue
|
|
398
|
+
okcms.service: admin
|
|
399
|
+
healthcheck: *admin-health
|
|
400
|
+
|
|
401
|
+
admin-green:
|
|
402
|
+
<<: *app
|
|
403
|
+
container_name: okcms-admin-green
|
|
404
|
+
command: ["admin"]
|
|
405
|
+
environment:
|
|
406
|
+
NODE_ENV: production
|
|
407
|
+
DB_HOST: postgres
|
|
408
|
+
REDIS_HOST: redis
|
|
409
|
+
PORT: \${ADMIN_PORT:-3011}
|
|
410
|
+
labels:
|
|
411
|
+
okcms.role: edge
|
|
412
|
+
okcms.lane: green
|
|
413
|
+
okcms.service: admin
|
|
414
|
+
healthcheck: *admin-health
|
|
415
|
+
|
|
416
|
+
# ------------------------------------------------------------- worker
|
|
417
|
+
# Uma fila não suporta duas instâncias "fantasma": o worker só sobe depois
|
|
418
|
+
# do swap e o da lane antiga drena com SIGTERM (stop_grace_period) antes.
|
|
419
|
+
worker-blue:
|
|
420
|
+
<<: *app
|
|
421
|
+
container_name: okcms-worker-blue
|
|
422
|
+
command: ["worker"]
|
|
423
|
+
stop_grace_period: 30s
|
|
424
|
+
labels:
|
|
425
|
+
okcms.role: worker
|
|
426
|
+
okcms.lane: blue
|
|
427
|
+
okcms.service: worker
|
|
428
|
+
|
|
429
|
+
worker-green:
|
|
430
|
+
<<: *app
|
|
431
|
+
container_name: okcms-worker-green
|
|
432
|
+
command: ["worker"]
|
|
433
|
+
stop_grace_period: 30s
|
|
434
|
+
labels:
|
|
435
|
+
okcms.role: worker
|
|
436
|
+
okcms.lane: green
|
|
437
|
+
okcms.service: worker
|
|
438
|
+
|
|
439
|
+
networks:
|
|
440
|
+
okcms:
|
|
441
|
+
name: okcms-net
|
|
442
|
+
external: true
|
|
443
|
+
|
|
444
|
+
volumes:
|
|
445
|
+
# compartilhado entre blue e green de propósito: mídia sobrevive ao deploy
|
|
446
|
+
storage-data:
|
|
447
|
+
name: okcms-storage
|
|
448
|
+
cache-data:
|
|
449
|
+
name: okcms-cache
|
|
450
|
+
`;
|
|
451
|
+
// ---------------------------------------------------------------------------
|
|
452
|
+
// Proxy nginx (projeto do operador: deploy/nginx/)
|
|
453
|
+
// ---------------------------------------------------------------------------
|
|
454
|
+
/**
|
|
455
|
+
* Roteamento production. Os upstreams ficam em arquivo SEPARADO
|
|
456
|
+
* (`00-upstreams.conf`) justamente para que o swap seja uma reescrita de um
|
|
457
|
+
* arquivo minúsculo + `nginx -s reload` — sem tocar no `server` block e sem
|
|
458
|
+
* reiniciar o container.
|
|
459
|
+
*
|
|
460
|
+
* `SERVER_NAME` / `ADMIN_SERVER_NAME` entram pelo envsubst nativo do nginx;
|
|
461
|
+
* as variáveis em `$host`/`$upstream_*` são do nginx e ficam intactas porque
|
|
462
|
+
* o envsubst só substitui a lista de variáveis definidas no ambiente.
|
|
463
|
+
*/
|
|
464
|
+
export const NGINX_TEMPLATE = `# @oktis-works - Roteamento do proxy OkCMS (template)
|
|
465
|
+
# Gerado pela CLI. Canônica em infrastructure/nginx/templates/default.conf.template
|
|
466
|
+
#
|
|
467
|
+
# O nginx oficial processa /etc/nginx/templates/*.template no start, com
|
|
468
|
+
# envsubst limitado às variáveis definidas no ambiente (SERVER_NAME e
|
|
469
|
+
# ADMIN_SERVER_NAME, vindas do .env). Tudo que for \$host, \$remote_addr ou
|
|
470
|
+
# \$upstream_* é do nginx e NÃO é substituído.
|
|
471
|
+
#
|
|
472
|
+
# Os upstreams moram em conf.d/00-upstreams.conf: a CLI os reescreve no swap
|
|
473
|
+
# e recarrega com \`nginx -s reload\` — troca de lane sem derrubar conexão.
|
|
474
|
+
|
|
475
|
+
map \$http_upgrade \$connection_upgrade {
|
|
476
|
+
default upgrade;
|
|
477
|
+
'' close;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
# ---------------------------------------------------------------- admin ----
|
|
481
|
+
# O admin é um Astro standalone servido na RAIZ (não em /admin): ele gera
|
|
482
|
+
# URLs /_astro/... e não tem base path configurado, então publicá-lo em
|
|
483
|
+
# /admin/ quebraria os assets. Ele precisa de host próprio.
|
|
484
|
+
server {
|
|
485
|
+
listen 80;
|
|
486
|
+
server_name \${ADMIN_SERVER_NAME};
|
|
487
|
+
|
|
488
|
+
location / {
|
|
489
|
+
proxy_pass \$upstream_admin;
|
|
490
|
+
proxy_http_version 1.1;
|
|
491
|
+
proxy_set_header Host \$host;
|
|
492
|
+
proxy_set_header X-Real-IP \$remote_addr;
|
|
493
|
+
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
494
|
+
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
495
|
+
proxy_set_header X-Forwarded-Host \$host;
|
|
496
|
+
proxy_set_header Upgrade \$http_upgrade;
|
|
497
|
+
proxy_set_header Connection \$connection_upgrade;
|
|
498
|
+
proxy_read_timeout 120s;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
# admin por porta — funciona sem DNS nenhum (http://<host>:8080/)
|
|
503
|
+
server {
|
|
504
|
+
listen 8080 default_server;
|
|
505
|
+
server_name _;
|
|
506
|
+
|
|
507
|
+
location / {
|
|
508
|
+
proxy_pass \$upstream_admin;
|
|
509
|
+
proxy_http_version 1.1;
|
|
510
|
+
proxy_set_header Host \$host;
|
|
511
|
+
proxy_set_header X-Real-IP \$remote_addr;
|
|
512
|
+
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
513
|
+
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
514
|
+
proxy_set_header Upgrade \$http_upgrade;
|
|
515
|
+
proxy_set_header Connection \$connection_upgrade;
|
|
516
|
+
proxy_read_timeout 120s;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
# ---------------------------------------------------------- site público ---
|
|
521
|
+
server {
|
|
522
|
+
listen 80 default_server;
|
|
523
|
+
server_name \${SERVER_NAME};
|
|
524
|
+
|
|
525
|
+
client_max_body_size 100m;
|
|
526
|
+
gzip on;
|
|
527
|
+
gzip_types text/plain text/css application/json application/javascript
|
|
528
|
+
image/svg+xml;
|
|
529
|
+
|
|
530
|
+
# healthcheck do edge: sempre no api (é quem fala com o banco)
|
|
531
|
+
location = /health {
|
|
532
|
+
access_log off;
|
|
533
|
+
proxy_pass \$upstream_api;
|
|
534
|
+
proxy_http_version 1.1;
|
|
535
|
+
proxy_set_header Host \$host;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
# API + mídia (/api/v1/media/file/<tenant>/<arquivo>)
|
|
539
|
+
location /api/ {
|
|
540
|
+
proxy_pass \$upstream_api;
|
|
541
|
+
proxy_http_version 1.1;
|
|
542
|
+
proxy_set_header Host \$host;
|
|
543
|
+
proxy_set_header X-Real-IP \$remote_addr;
|
|
544
|
+
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
545
|
+
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
546
|
+
proxy_set_header X-Forwarded-Host \$host;
|
|
547
|
+
proxy_set_header Upgrade \$http_upgrade;
|
|
548
|
+
proxy_set_header Connection \$connection_upgrade;
|
|
549
|
+
proxy_read_timeout 120s;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
# STORAGE_PUBLIC_BASE=/storage → rota o mesmo arquivo para a API.
|
|
553
|
+
# O local driver publica <base>/<tenant>/<arquivo>, que é exatamente o
|
|
554
|
+
# formato de /api/v1/media/file/<tenant>/<arquivo>.
|
|
555
|
+
location /storage/ {
|
|
556
|
+
rewrite ^/storage/(.*)\$ /api/v1/media/file/\$1 break;
|
|
557
|
+
proxy_pass \$upstream_api;
|
|
558
|
+
proxy_http_version 1.1;
|
|
559
|
+
proxy_set_header Host \$host;
|
|
560
|
+
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
561
|
+
proxy_cache_valid 200 30d;
|
|
562
|
+
expires 30d;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
# assets de tema servidos pelo web (resolver de path do Theme SDK)
|
|
566
|
+
location /themes/ {
|
|
567
|
+
proxy_pass \$upstream_web;
|
|
568
|
+
proxy_http_version 1.1;
|
|
569
|
+
proxy_set_header Host \$host;
|
|
570
|
+
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
# SSR do site público
|
|
574
|
+
location / {
|
|
575
|
+
proxy_pass \$upstream_web;
|
|
576
|
+
proxy_http_version 1.1;
|
|
577
|
+
proxy_set_header Host \$host;
|
|
578
|
+
proxy_set_header X-Real-IP \$remote_addr;
|
|
579
|
+
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
580
|
+
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
581
|
+
proxy_set_header X-Forwarded-Host \$host;
|
|
582
|
+
proxy_set_header Upgrade \$http_upgrade;
|
|
583
|
+
proxy_set_header Connection \$connection_upgrade;
|
|
584
|
+
proxy_read_timeout 120s;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
`;
|
|
588
|
+
export const DEFAULT_UPSTREAM_PORTS = { api: 3000, web: 3001, admin: 3011 };
|
|
589
|
+
/**
|
|
590
|
+
* Conteúdo de `deploy/nginx/conf.d/00-upstreams.conf`.
|
|
591
|
+
*
|
|
592
|
+
* Só a linha de cada `map` muda no swap — é esse o truque do blue/green via
|
|
593
|
+
* arquivo: reescrever 3 linhas é atômico e `nginx -s reload` re-lê sem
|
|
594
|
+
* fechar conexão estabelecida.
|
|
595
|
+
*/
|
|
596
|
+
export function renderUpstreams(lane, ports = DEFAULT_UPSTREAM_PORTS) {
|
|
597
|
+
return `# @oktis-works - Upstreams ativos (GERADO pela CLI — não edite à mão)
|
|
598
|
+
#
|
|
599
|
+
# Reescrito pelo \`okcms update\` no momento do swap e recarregado com
|
|
600
|
+
# docker exec okcms-proxy nginx -s reload
|
|
601
|
+
# Aponta para containers do projeto Docker okcms-<lane> na rede okcms-net.
|
|
602
|
+
# Mídia, cache e banco NÃO são blue/green — moram no projeto \`okcms\`.
|
|
603
|
+
|
|
604
|
+
map \$host \$upstream_api {
|
|
605
|
+
default http://okcms-api-${lane}:${ports.api};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
map \$host \$upstream_web {
|
|
609
|
+
default http://okcms-web-${lane}:${ports.web};
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
map \$host \$upstream_admin {
|
|
613
|
+
default http://okcms-admin-${lane}:${ports.admin};
|
|
614
|
+
}
|
|
615
|
+
`;
|
|
616
|
+
}
|
|
617
|
+
// ---------------------------------------------------------------------------
|
|
618
|
+
// Caminhos no projeto do operador + montagem dos assets
|
|
619
|
+
// ---------------------------------------------------------------------------
|
|
620
|
+
export const DEPLOY_PATHS = {
|
|
621
|
+
dockerignore: '.dockerignore',
|
|
622
|
+
dockerfile: 'docker/Dockerfile',
|
|
623
|
+
entrypoint: 'docker/entrypoint.sh',
|
|
624
|
+
composeInfra: 'docker-compose.infra.yml',
|
|
625
|
+
composeDeploy: 'docker-compose.deploy.yml',
|
|
626
|
+
nginxTemplate: 'deploy/nginx/templates/default.conf.template',
|
|
627
|
+
nginxUpstreams: 'deploy/nginx/conf.d/00-upstreams.conf',
|
|
628
|
+
};
|
|
629
|
+
/** Arquivo gerado pelo envsubst no start do nginx — fica fora do git. */
|
|
630
|
+
export const NGINX_GENERATED = 'deploy/nginx/conf.d/default.conf';
|
|
631
|
+
/** Entradas de `.gitignore` que evitam commitar artefato gerado. */
|
|
632
|
+
export const DEPLOY_GITIGNORE = [
|
|
633
|
+
NGINX_GENERATED,
|
|
634
|
+
'docker-compose.override.yml',
|
|
635
|
+
'.data/',
|
|
636
|
+
// estado de runtime da lane ativa — varia por máquina, conflita em merge
|
|
637
|
+
'.deploy/',
|
|
638
|
+
];
|
|
639
|
+
/** Lê as portas reais do `.env`, com fallback para os defaults dos apps. */
|
|
640
|
+
export function portsFromEnv(record) {
|
|
641
|
+
const port = (value, fallback) => {
|
|
642
|
+
const parsed = Number(value);
|
|
643
|
+
return Number.isInteger(parsed) && parsed > 0 && parsed <= 65535 ? parsed : fallback;
|
|
644
|
+
};
|
|
645
|
+
return {
|
|
646
|
+
api: port(record['PORT'], DEFAULT_UPSTREAM_PORTS.api),
|
|
647
|
+
web: port(record['WEB_PORT'], DEFAULT_UPSTREAM_PORTS.web),
|
|
648
|
+
admin: port(record['ADMIN_PORT'], DEFAULT_UPSTREAM_PORTS.admin),
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Todos os arquivos de deploy do projeto, prontos para escrita.
|
|
653
|
+
* `lane`/`ports` só definem o upstream INICIAL (lane vazia → blue); depois
|
|
654
|
+
* disso quem reescreve esse arquivo é o `okcms update`, a cada swap.
|
|
655
|
+
*
|
|
656
|
+
* `00-upstreams.conf` é versionado de propósito (sem segredo, aponta para um
|
|
657
|
+
* container que ainda vai existir) — sem ele um clone limpo sobe um nginx
|
|
658
|
+
* sem upstream nenhum. Já `default.conf` é gerado pelo envsubst e fica fora.
|
|
659
|
+
*/
|
|
660
|
+
export function deployAssets(lane = 'blue', ports = DEFAULT_UPSTREAM_PORTS) {
|
|
661
|
+
return [
|
|
662
|
+
{ path: DEPLOY_PATHS.dockerignore, content: DOCKERIGNORE },
|
|
663
|
+
{ path: DEPLOY_PATHS.dockerfile, content: DOCKERFILE },
|
|
664
|
+
{ path: DEPLOY_PATHS.entrypoint, content: ENTRYPOINT_SH, mode: 0o755 },
|
|
665
|
+
{ path: DEPLOY_PATHS.composeInfra, content: COMPOSE_INFRA },
|
|
666
|
+
{ path: DEPLOY_PATHS.composeDeploy, content: COMPOSE_DEPLOY },
|
|
667
|
+
{ path: DEPLOY_PATHS.nginxTemplate, content: NGINX_TEMPLATE },
|
|
668
|
+
{ path: DEPLOY_PATHS.nginxUpstreams, content: renderUpstreams(lane, ports) },
|
|
669
|
+
];
|
|
670
|
+
}
|
|
671
|
+
//# sourceMappingURL=assets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets.js","sourceRoot":"","sources":["../src/assets.ts"],"names":[],"mappings":"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"}
|