From e52cac7444be137451ae639fb536d1f2259c66b8 Mon Sep 17 00:00:00 2001 From: Mars <3361409208a@gmail.com> Date: Mon, 8 Jun 2026 16:24:06 +0800 Subject: [PATCH] feat(deploy): add Docker full-stack deployment and server pack scripts Enable one-click production deploy via docker-compose.prod.yml, with deployment docs and zip packaging for Baota upload. Co-authored-by: Cursor --- .dockerignore | 20 ++++ .env.docker.example | 19 ++++ README.md | 3 +- docker-compose.prod.yml | 100 +++++++++++++++++ docker/admin/Dockerfile | 28 +++++ docker/api/Dockerfile | 46 ++++++++ docker/api/entrypoint.sh | 25 +++++ docker/nginx/admin.conf | 22 ++++ docker/nginx/player.conf | 31 ++++++ docker/player/Dockerfile | 28 +++++ docs/Docker部署指南.md | 229 +++++++++++++++++++++++++++++++++++++++ docs/项目启动指南.md | 12 ++ pack.bat | 23 ++++ pack.mjs | 63 +++++++++++ package.json | 7 +- scripts/docker-up.sh | 19 ++++ 16 files changed, 673 insertions(+), 2 deletions(-) create mode 100644 .dockerignore create mode 100644 .env.docker.example create mode 100644 docker-compose.prod.yml create mode 100644 docker/admin/Dockerfile create mode 100644 docker/api/Dockerfile create mode 100644 docker/api/entrypoint.sh create mode 100644 docker/nginx/admin.conf create mode 100644 docker/nginx/player.conf create mode 100644 docker/player/Dockerfile create mode 100644 docs/Docker部署指南.md create mode 100644 pack.bat create mode 100644 pack.mjs create mode 100644 scripts/docker-up.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6bf3d41 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,20 @@ +node_modules +**/node_modules +**/dist +.git +.gitignore +.env +.env.* +!.env.example +!.env.docker.example +*.log +coverage +.turbo +**/*.tsbuildinfo +apps/player/dist +apps/admin/dist +apps/api/dist +mcps +assets +agent-transcripts +**/.vite diff --git a/.env.docker.example b/.env.docker.example new file mode 100644 index 0000000..f05dd25 --- /dev/null +++ b/.env.docker.example @@ -0,0 +1,19 @@ +# 复制为 .env.docker 后修改 +# cp .env.docker.example .env.docker + +# PostgreSQL(生产务必修改) +POSTGRES_PASSWORD=thebet365 + +# JWT(生产务必修改) +JWT_SECRET=change-me-in-production-use-long-random-string +JWT_PLAYER_EXPIRES=24h +JWT_ADMIN_EXPIRES=2h +JWT_AGENT_EXPIRES=8h + +# 首次部署写入演示账号与默认数据,完成后改为 false +SEED_DATABASE=true + +# 对外端口(宝塔/Nginx 反代时可改回 80/443 或保留以下端口) +API_PORT=3000 +PLAYER_PORT=8080 +ADMIN_PORT=8081 diff --git a/README.md b/README.md index fd7c5cd..47028f5 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ Monorepo 项目,包含 NestJS 后端与 Vue 3 三端前端。 ## 快速开始 -> 完整步骤、环境变量、端口、演示账号与排错见 **[docs/项目启动指南.md](docs/项目启动指南.md)**。 +> 完整步骤、环境变量、端口、演示账号与排错见 **[docs/项目启动指南.md](docs/项目启动指南.md)**。 +> **Docker 全栈一键部署(生产)** 见 **[docs/Docker部署指南.md](docs/Docker部署指南.md)**。 ### 1. 启动基础设施 diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..5c818a2 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,100 @@ +# TheBet365 全栈生产部署 +# 一键启动: docker compose -f docker-compose.prod.yml up -d --build +# 首次部署建议 SEED_DATABASE=true(见 .env.docker) + +services: + postgres: + image: postgres:16-alpine + container_name: thebet365-postgres + environment: + POSTGRES_USER: thebet365 + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-thebet365} + POSTGRES_DB: thebet365 + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U thebet365'] + interval: 5s + timeout: 5s + retries: 10 + restart: unless-stopped + networks: + - thebet365 + + redis: + image: redis:7-alpine + container_name: thebet365-redis + volumes: + - redis_data:/data + healthcheck: + test: ['CMD', 'redis-cli', 'ping'] + interval: 5s + timeout: 5s + retries: 5 + restart: unless-stopped + networks: + - thebet365 + + api: + build: + context: . + dockerfile: docker/api/Dockerfile + container_name: thebet365-api + environment: + DATABASE_URL: postgresql://thebet365:${POSTGRES_PASSWORD:-thebet365}@postgres:5432/thebet365 + REDIS_URL: redis://redis:6379 + JWT_SECRET: ${JWT_SECRET:-change-me-in-production-use-long-random-string} + JWT_PLAYER_EXPIRES: ${JWT_PLAYER_EXPIRES:-24h} + JWT_ADMIN_EXPIRES: ${JWT_ADMIN_EXPIRES:-2h} + JWT_AGENT_EXPIRES: ${JWT_AGENT_EXPIRES:-8h} + PORT: 3000 + NODE_ENV: production + UPLOAD_DIR: /app/uploads + SEED_DATABASE: ${SEED_DATABASE:-false} + volumes: + - uploads_data:/app/uploads + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + ports: + - '${API_PORT:-3000}:3000' + restart: unless-stopped + networks: + - thebet365 + + player: + build: + context: . + dockerfile: docker/player/Dockerfile + container_name: thebet365-player + depends_on: + - api + ports: + - '${PLAYER_PORT:-8080}:80' + restart: unless-stopped + networks: + - thebet365 + + admin: + build: + context: . + dockerfile: docker/admin/Dockerfile + container_name: thebet365-admin + depends_on: + - api + ports: + - '${ADMIN_PORT:-8081}:80' + restart: unless-stopped + networks: + - thebet365 + +networks: + thebet365: + name: thebet365 + +volumes: + postgres_data: + redis_data: + uploads_data: diff --git a/docker/admin/Dockerfile b/docker/admin/Dockerfile new file mode 100644 index 0000000..872e149 --- /dev/null +++ b/docker/admin/Dockerfile @@ -0,0 +1,28 @@ +FROM node:20-alpine AS base +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable + +FROM base AS deps +WORKDIR /app +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +COPY apps/api/package.json apps/api/ +COPY apps/player/package.json apps/player/ +COPY apps/admin/package.json apps/admin/ +COPY packages/shared/package.json packages/shared/ +RUN pnpm install --frozen-lockfile + +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY --from=deps /app/apps/api/node_modules ./apps/api/node_modules +COPY --from=deps /app/apps/player/node_modules ./apps/player/node_modules +COPY --from=deps /app/apps/admin/node_modules ./apps/admin/node_modules +COPY --from=deps /app/packages/shared/node_modules ./packages/shared/node_modules +COPY . . +RUN pnpm --filter @thebet365/admin build + +FROM nginx:1.27-alpine +COPY docker/nginx/admin.conf /etc/nginx/conf.d/default.conf +COPY --from=builder /app/apps/admin/dist /usr/share/nginx/html +EXPOSE 80 diff --git a/docker/api/Dockerfile b/docker/api/Dockerfile new file mode 100644 index 0000000..ada0bb2 --- /dev/null +++ b/docker/api/Dockerfile @@ -0,0 +1,46 @@ +FROM node:20-alpine AS base +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable + +FROM base AS deps +WORKDIR /app +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +COPY apps/api/package.json apps/api/ +COPY apps/player/package.json apps/player/ +COPY apps/admin/package.json apps/admin/ +COPY packages/shared/package.json packages/shared/ +RUN pnpm install --frozen-lockfile + +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY --from=deps /app/apps/api/node_modules ./apps/api/node_modules +COPY --from=deps /app/apps/player/node_modules ./apps/player/node_modules +COPY --from=deps /app/apps/admin/node_modules ./apps/admin/node_modules +COPY --from=deps /app/packages/shared/node_modules ./packages/shared/node_modules +COPY . . +RUN pnpm --filter @thebet365/shared build +RUN pnpm --filter @thebet365/api exec prisma generate +RUN pnpm --filter @thebet365/api build + +FROM base AS runner +WORKDIR /app +ENV NODE_ENV=production + +COPY --from=builder /app/package.json ./package.json +COPY --from=builder /app/pnpm-workspace.yaml ./pnpm-workspace.yaml +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/packages/shared ./packages/shared +COPY --from=builder /app/apps/api/dist ./apps/api/dist +COPY --from=builder /app/apps/api/package.json ./apps/api/package.json +COPY --from=builder /app/apps/api/prisma ./apps/api/prisma +COPY --from=builder /app/apps/api/node_modules ./apps/api/node_modules + +COPY uploads/banners ./uploads-default/banners +COPY docker/api/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +WORKDIR /app/apps/api +EXPOSE 3000 +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/api/entrypoint.sh b/docker/api/entrypoint.sh new file mode 100644 index 0000000..606fdf7 --- /dev/null +++ b/docker/api/entrypoint.sh @@ -0,0 +1,25 @@ +#!/bin/sh +set -e + +cd /app/apps/api + +echo "[api] running migrations..." +until npx prisma migrate deploy; do + echo "[api] waiting for database..." + sleep 2 +done +npx prisma generate + +if [ "$SEED_DATABASE" = "true" ]; then + echo "[api] seeding database..." + npx prisma db seed +fi + +if [ -d /app/uploads-default/banners ] && [ ! -f /app/uploads/banners/welcome.svg ]; then + echo "[api] copying default banner uploads..." + mkdir -p /app/uploads/banners + cp -r /app/uploads-default/banners/. /app/uploads/banners/ +fi + +echo "[api] starting server..." +exec node dist/main.js diff --git a/docker/nginx/admin.conf b/docker/nginx/admin.conf new file mode 100644 index 0000000..e7f4ca9 --- /dev/null +++ b/docker/nginx/admin.conf @@ -0,0 +1,22 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml; + + location /api/ { + proxy_pass http://api:3000; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/docker/nginx/player.conf b/docker/nginx/player.conf new file mode 100644 index 0000000..35cb6a5 --- /dev/null +++ b/docker/nginx/player.conf @@ -0,0 +1,31 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml; + + location /api/ { + proxy_pass http://api:3000; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /uploads/ { + proxy_pass http://api:3000; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/docker/player/Dockerfile b/docker/player/Dockerfile new file mode 100644 index 0000000..83437fe --- /dev/null +++ b/docker/player/Dockerfile @@ -0,0 +1,28 @@ +FROM node:20-alpine AS base +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable + +FROM base AS deps +WORKDIR /app +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +COPY apps/api/package.json apps/api/ +COPY apps/player/package.json apps/player/ +COPY apps/admin/package.json apps/admin/ +COPY packages/shared/package.json packages/shared/ +RUN pnpm install --frozen-lockfile + +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY --from=deps /app/apps/api/node_modules ./apps/api/node_modules +COPY --from=deps /app/apps/player/node_modules ./apps/player/node_modules +COPY --from=deps /app/apps/admin/node_modules ./apps/admin/node_modules +COPY --from=deps /app/packages/shared/node_modules ./packages/shared/node_modules +COPY . . +RUN pnpm --filter @thebet365/player build + +FROM nginx:1.27-alpine +COPY docker/nginx/player.conf /etc/nginx/conf.d/default.conf +COPY --from=builder /app/apps/player/dist /usr/share/nginx/html +EXPOSE 80 diff --git a/docs/Docker部署指南.md b/docs/Docker部署指南.md new file mode 100644 index 0000000..2c133d3 --- /dev/null +++ b/docs/Docker部署指南.md @@ -0,0 +1,229 @@ +# TheBet365 Docker 全栈部署指南 + +本文档说明如何使用 Docker **一键部署** API、玩家前台、管理后台、PostgreSQL 与 Redis。 + +> 本地开发仍可使用根目录 `docker-compose.yml` 仅启动数据库,应用在本机 `pnpm dev` 运行。 + +--- + +## 一、架构 + +```text +浏览器 + ├─ :8080 player (Nginx) ── /api、/uploads ──► api (NestJS :3000) + └─ :8081 admin (Nginx) ── /api ────────────► api (NestJS :3000) + +api ──► postgres:5432 + └──► redis:6379 +``` + +| 容器 | 默认端口 | 说明 | +|------|----------|------| +| `thebet365-player` | 8080 | 玩家 H5 前台 | +| `thebet365-admin` | 8081 | 管理后台(平台 + 代理) | +| `thebet365-api` | 3000 | NestJS API / Swagger | +| `thebet365-postgres` | 不对外暴露 | PostgreSQL 16 | +| `thebet365-redis` | 不对外暴露 | Redis 7 | + +--- + +## 二、服务器要求 + +| 项目 | 要求 | +|------|------| +| 系统 | Linux(推荐 Ubuntu 22.04+) | +| Docker | 24+ | +| Docker Compose | v2(`docker compose`) | +| 内存 | 建议 ≥ 2 GB(首次构建需更多) | +| 磁盘 | 建议 ≥ 10 GB | + +宝塔面板:左侧 **Docker** → 确认 Docker 服务已安装并运行。 + +--- + +## 三、一键部署(命令行) + +### 1. 上传代码 + +将项目放到服务器,例如 `/www/wwwroot/thebet365`(宝塔 **文件** 上传或 `git clone`)。 + +### 2. 配置环境变量 + +```bash +cd /www/wwwroot/thebet365 +cp .env.docker.example .env.docker +``` + +**生产环境务必修改:** + +- `POSTGRES_PASSWORD` — 数据库密码 +- `JWT_SECRET` — 足够长的随机字符串 +- 首次部署可保持 `SEED_DATABASE=true`,写入演示账号与默认数据;**种子跑完后改回 `false` 并重启 api** + +### 3. 构建并启动 + +```bash +docker compose -f docker-compose.prod.yml --env-file .env.docker up -d --build +``` + +或使用脚本: + +```bash +chmod +x scripts/docker-up.sh +./scripts/docker-up.sh +``` + +### 4. 查看状态 + +```bash +docker compose -f docker-compose.prod.yml --env-file .env.docker ps +docker compose -f docker-compose.prod.yml --env-file .env.docker logs -f api +``` + +启动成功后访问: + +| 服务 | 地址 | +|------|------| +| 玩家前台 | http://服务器IP:8080 | +| 管理后台 | http://服务器IP:8081 | +| API 文档 | http://服务器IP:3000/api/docs | + +--- + +## 四、宝塔面板操作 + +### 方式 A:容器编排(推荐) + +1. **文件** — 上传/克隆项目到 `/www/wwwroot/thebet365` +2. **终端** — 执行 `cp .env.docker.example .env.docker` 并编辑密钥 +3. **Docker** → **容器编排** → **添加** + - 编排文件:选择 `docker-compose.prod.yml` + - 环境文件:选择 `.env.docker`(若面板支持) +4. **构建并启动** + +若面板不支持指定 env 文件,在 **终端** 中执行第三节第 3 步命令即可。 + +### 方式 B:绑定域名(Nginx 反代) + +在宝塔 **网站** 中为玩家站、管理站分别建站,**不**使用 PHP,只做反向代理: + +**玩家站**(根目录可留空或任意): + +```nginx +location / { + proxy_pass http://127.0.0.1:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; +} +``` + +**管理站** — 将 `8080` 改为 `8081`。 + +API 端口 3000 建议**不要**直接公网暴露;Swagger 仅供内网或通过 VPN 访问。 + +--- + +## 五、演示账号 + +`SEED_DATABASE=true` 首次启动后可用(详见 [默认数据说明.md](./默认数据说明.md)): + +| 角色 | 用户名 | 密码 | 入口 | +|------|--------|------|------| +| 平台管理员 | admin | Admin@123 | 管理后台 :8081 | +| 一级代理 | agent1 | Agent@123 | 管理后台 :8081 | +| 玩家 | player1 | Player@123 | 玩家前台 :8080 | + +--- + +## 六、常用命令 + +```bash +# 停止 +docker compose -f docker-compose.prod.yml --env-file .env.docker down + +# 停止并删除数据卷(清空数据库,慎用) +docker compose -f docker-compose.prod.yml --env-file .env.docker down -v + +# 仅重建 API +docker compose -f docker-compose.prod.yml --env-file .env.docker up -d --build api + +# 手动种子(容器已运行时) +docker compose -f docker-compose.prod.yml --env-file .env.docker exec api npx prisma db seed + +# 查看 API 日志 +docker compose -f docker-compose.prod.yml --env-file .env.docker logs -f api +``` + +根目录 `package.json` 快捷脚本(需已存在 `.env.docker`): + +```bash +pnpm docker:up +pnpm docker:down +pnpm docker:logs +pnpm docker:ps +``` + +--- + +## 七、数据持久化 + +| 卷名 | 内容 | +|------|------| +| `postgres_data` | 数据库 | +| `redis_data` | Redis | +| `uploads_data` | Banner 等用户上传文件 | + +备份 PostgreSQL 示例: + +```bash +docker exec thebet365-postgres pg_dump -U thebet365 thebet365 > backup.sql +``` + +--- + +## 八、更新部署 + +```bash +cd /www/wwwroot/thebet365 +git pull # 或重新上传代码 +docker compose -f docker-compose.prod.yml --env-file .env.docker up -d --build +``` + +API 容器启动时会自动执行 `prisma migrate deploy`,无需手动迁移。 + +--- + +## 九、故障排查 + +### 1. API 一直重启 + +```bash +docker logs thebet365-api +``` + +常见原因:数据库未就绪(稍等重试)、`DATABASE_URL` 密码与 `POSTGRES_PASSWORD` 不一致。 + +### 2. 前端 502 / 接口失败 + +确认 `thebet365-api` 为 running,且 player/admin 容器能解析主机名 `api`(同一 compose 网络)。 + +### 3. 构建慢或内存不足 + +首次 `docker compose build` 会安装 pnpm 依赖并编译三端,建议服务器 ≥ 2 GB 内存;可在低峰期构建。 + +### 4. 端口被占用 + +修改 `.env.docker` 中的 `API_PORT` / `PLAYER_PORT` / `ADMIN_PORT` 后重新 `up -d`。 + +--- + +## 十、与本地开发的区别 + +| 场景 | 命令 | +|------|------| +| 本地开发(仅 DB 用 Docker) | `docker compose up -d` + `pnpm dev` | +| 生产全栈 Docker | `docker compose -f docker-compose.prod.yml --env-file .env.docker up -d --build` | + +相关文档:[项目启动指南.md](./项目启动指南.md) diff --git a/docs/项目启动指南.md b/docs/项目启动指南.md index a2aedfc..14449a7 100644 --- a/docs/项目启动指南.md +++ b/docs/项目启动指南.md @@ -242,6 +242,17 @@ pnpm dev:player ## 八、生产构建(简述) +### 方式 A:Docker 全栈一键部署(推荐) + +```bash +cp .env.docker.example .env.docker # 修改 POSTGRES_PASSWORD、JWT_SECRET +docker compose -f docker-compose.prod.yml --env-file .env.docker up -d --build +``` + +玩家 `:8080` · 管理 `:8081` · API `:3000`。完整说明见 **[Docker部署指南.md](./Docker部署指南.md)**。 + +### 方式 B:宿主机 + Nginx + ```bash pnpm install pnpm build @@ -335,6 +346,7 @@ pnpm db:generate && pnpm db:migrate ## 十一、相关文档 - [README.md](../README.md) — 项目概览与技术栈 +- [Docker部署指南.md](./Docker部署指南.md) — Docker 全栈生产部署 - [默认数据说明.md](./默认数据说明.md) — seed 后的账号、赛事、夺冠盘、运营内容 - [apps/admin/README.md](../apps/admin/README.md) — 管理后台结构 - [足球投注平台产品需求与MVP实施计划_PRD_v1.2.md](../足球投注平台产品需求与MVP实施计划_PRD_v1.2.md) — 产品需求(若存在) diff --git a/pack.bat b/pack.bat new file mode 100644 index 0000000..f55cb54 --- /dev/null +++ b/pack.bat @@ -0,0 +1,23 @@ +@echo off +setlocal +cd /d "%~dp0" + +where node >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Node.js not found. Install Node.js 20+ first. + pause + exit /b 1 +) + +node pack.mjs +set EXIT_CODE=%ERRORLEVEL% + +echo. +if not %EXIT_CODE%==0 ( + echo Pack failed. + pause + exit /b %EXIT_CODE% +) + +pause +exit /b 0 diff --git a/pack.mjs b/pack.mjs new file mode 100644 index 0000000..fe45a27 --- /dev/null +++ b/pack.mjs @@ -0,0 +1,63 @@ +#!/usr/bin/env node +/** + * 生成部署用 zip,排除 node_modules、构建产物、.git、本地 .env 等。 + * 用法: node pack.mjs + * 输出: release/thebet365-deploy-YYYYMMDD-HHmmss.zip + */ +import { execSync } from 'node:child_process'; +import { mkdirSync, statSync } from 'node:fs'; +import { basename, join } from 'node:path'; + +const root = process.cwd(); +const projectName = basename(root); +const parent = join(root, '..'); + +const stamp = new Date() + .toISOString() + .replace(/[-:]/g, '') + .slice(0, 15) + .replace('T', '-'); + +const outDir = join(root, 'release'); +const outFile = join(outDir, `thebet365-deploy-${stamp}.zip`); + +/** 相对项目根目录的路径 */ +const excludePaths = [ + 'node_modules', + '.git', + '.env', + '.env.local', + '.env.docker', + 'apps/api/.env', + 'apps/api/dist', + 'apps/player/dist', + 'apps/admin/dist', + 'release', +]; + +mkdirSync(outDir, { recursive: true }); + +const excludeArgs = excludePaths + .map((p) => `--exclude=${projectName}/${p}`) + .join(' '); + +const cmd = `tar -a -c -f "${outFile}" ${excludeArgs} -C "${parent}" "${projectName}"`; + +console.log('Packing deployment archive...'); +console.log('Excluding:'); +for (const p of excludePaths) console.log(` - ${p}/`); +console.log(''); + +try { + execSync(cmd, { stdio: 'inherit' }); +} catch { + console.error('\nPack failed. Ensure tar is available (Windows 10+ / Linux / macOS).'); + process.exit(1); +} + +const sizeMb = (statSync(outFile).size / (1024 * 1024)).toFixed(2); +console.log(`\nDone: ${outFile}`); +console.log(`Size: ${sizeMb} MB`); +console.log('\nUpload to server, extract under /www/wwwroot/thebet365, then:'); +console.log(' cp .env.docker.example .env.docker'); +console.log(' docker compose -f docker-compose.prod.yml --env-file .env.docker up -d --build'); diff --git a/package.json b/package.json index d239bbf..0f96be1 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,12 @@ "db:migrate": "pnpm --filter @thebet365/api db:migrate", "db:migrate:deploy": "pnpm --filter @thebet365/api db:migrate:deploy", "db:seed": "pnpm --filter @thebet365/api db:seed", - "db:studio": "pnpm --filter @thebet365/api db:studio" + "db:studio": "pnpm --filter @thebet365/api db:studio", + "docker:up": "docker compose -f docker-compose.prod.yml --env-file .env.docker up -d --build", + "docker:down": "docker compose -f docker-compose.prod.yml --env-file .env.docker down", + "docker:logs": "docker compose -f docker-compose.prod.yml --env-file .env.docker logs -f", + "docker:ps": "docker compose -f docker-compose.prod.yml --env-file .env.docker ps", + "pack:deploy": "node pack.mjs" }, "engines": { "node": ">=20" diff --git a/scripts/docker-up.sh b/scripts/docker-up.sh new file mode 100644 index 0000000..fa1b985 --- /dev/null +++ b/scripts/docker-up.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env sh +set -e + +cd "$(dirname "$0")/.." + +if [ ! -f .env.docker ]; then + cp .env.docker.example .env.docker + echo "Created .env.docker from example — please review secrets before production use." +fi + +docker compose -f docker-compose.prod.yml --env-file .env.docker up -d --build + +echo "" +echo "TheBet365 stack is starting." +echo " Player: http://localhost:${PLAYER_PORT:-8080}" +echo " Admin: http://localhost:${ADMIN_PORT:-8081}" +echo " API: http://localhost:${API_PORT:-3000}/api/docs" +echo "" +echo "Check status: docker compose -f docker-compose.prod.yml ps"