gengx
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
# Copy to deploy/.env.production and replace every CHANGE_ME value.
|
||||
APP_DOMAIN=app.example.com
|
||||
ADMIN_DOMAIN=admin.example.com
|
||||
API_DOMAIN=api.example.com
|
||||
|
||||
MYSQL_DATABASE=im
|
||||
MYSQL_USER=xingyu_app
|
||||
MYSQL_PASSWORD=CHANGE_ME_DATABASE_PASSWORD
|
||||
MYSQL_ROOT_PASSWORD=CHANGE_ME_ROOT_PASSWORD
|
||||
|
||||
IM_JWT_SECRET=CHANGE_ME_AT_LEAST_64_RANDOM_CHARACTERS
|
||||
IM_CONFIG_ENCRYPTION_KEY=CHANGE_ME_DIFFERENT_64_RANDOM_CHARACTERS
|
||||
IM_BOOTSTRAP_ADMIN_USERNAME=admin
|
||||
IM_BOOTSTRAP_ADMIN_PASSWORD=CHANGE_ME_12_CHARS_UPPER_LOWER_DIGIT_SPECIAL
|
||||
IM_BOOTSTRAP_ADMIN_REAL_NAME=平台管理员
|
||||
|
||||
# Set blank after the first successful bootstrap if desired.
|
||||
TZ=Asia/Shanghai
|
||||
@@ -0,0 +1,18 @@
|
||||
FROM golang:1.24-alpine AS builder
|
||||
WORKDIR /src
|
||||
COPY backend/go.mod backend/go.sum ./
|
||||
RUN go mod download
|
||||
COPY backend/ ./
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/xingyu-api ./cmd/server
|
||||
|
||||
FROM alpine:3.22
|
||||
RUN apk add --no-cache ca-certificates tzdata \
|
||||
&& addgroup -S xingyu \
|
||||
&& adduser -S -G xingyu xingyu \
|
||||
&& mkdir -p /data/uploads \
|
||||
&& chown -R xingyu:xingyu /data
|
||||
WORKDIR /app
|
||||
COPY --from=builder /out/xingyu-api /app/xingyu-api
|
||||
USER xingyu
|
||||
EXPOSE 8888
|
||||
ENTRYPOINT ["/app/xingyu-api"]
|
||||
@@ -0,0 +1,29 @@
|
||||
FROM node:22-alpine AS mobile-builder
|
||||
WORKDIR /src/mobile
|
||||
ARG VITE_API_ORIGIN
|
||||
ARG VITE_WS_ORIGIN
|
||||
ENV VITE_API_ORIGIN=${VITE_API_ORIGIN}
|
||||
ENV VITE_WS_ORIGIN=${VITE_WS_ORIGIN}
|
||||
COPY mobile/package.json mobile/package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY mobile/ ./
|
||||
RUN npm run build:h5
|
||||
|
||||
FROM node:22-alpine AS admin-builder
|
||||
WORKDIR /src/admin
|
||||
ENV CI=true
|
||||
RUN corepack enable
|
||||
COPY admin/package.json admin/pnpm-lock.yaml admin/pnpm-workspace.yaml ./
|
||||
COPY admin/.npmrc ./
|
||||
COPY admin/internal ./internal
|
||||
COPY admin/packages ./packages
|
||||
COPY admin/scripts ./scripts
|
||||
COPY admin/apps/web-ele ./apps/web-ele
|
||||
RUN pnpm install --frozen-lockfile
|
||||
RUN pnpm --filter @vben/web-ele build
|
||||
|
||||
FROM nginx:1.28-alpine
|
||||
COPY --from=mobile-builder /src/mobile/dist/build/h5 /srv/mobile
|
||||
COPY --from=admin-builder /src/admin/apps/web-ele/dist /srv/admin
|
||||
COPY deploy/nginx.conf.template /etc/nginx/templates/default.conf.template
|
||||
EXPOSE 80 443
|
||||
@@ -0,0 +1,98 @@
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8.4
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MYSQL_DATABASE: ${MYSQL_DATABASE}
|
||||
MYSQL_USER: ${MYSQL_USER}
|
||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
command: ["--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci"]
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "MYSQL_PWD=$${MYSQL_PASSWORD} mysqladmin ping -h 127.0.0.1 -u$${MYSQL_USER} --silent"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
networks: [internal]
|
||||
|
||||
migrate:
|
||||
image: mysql:8.4
|
||||
restart: "no"
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
MYSQL_DATABASE: ${MYSQL_DATABASE}
|
||||
MYSQL_USER: ${MYSQL_USER}
|
||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
||||
volumes:
|
||||
- ../backend/migrations:/migrations:ro
|
||||
- ./migrate.sh:/migrate.sh:ro
|
||||
entrypoint: ["sh", "/migrate.sh"]
|
||||
networks: [internal]
|
||||
|
||||
api:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deploy/Dockerfile.backend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
environment:
|
||||
IM_ENV: production
|
||||
IM_HOST: 0.0.0.0
|
||||
IM_PORT: 8888
|
||||
IM_DB_DSN: ${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(mysql:3306)/${MYSQL_DATABASE}?charset=utf8mb4&parseTime=True&loc=Local
|
||||
IM_JWT_SECRET: ${IM_JWT_SECRET}
|
||||
IM_CONFIG_ENCRYPTION_KEY: ${IM_CONFIG_ENCRYPTION_KEY}
|
||||
IM_ALLOWED_ORIGINS: https://${APP_DOMAIN},https://${ADMIN_DOMAIN}
|
||||
IM_SEED_DEMO: "false"
|
||||
IM_MEDIA_DIR: /data/uploads
|
||||
IM_BOOTSTRAP_ADMIN_USERNAME: ${IM_BOOTSTRAP_ADMIN_USERNAME}
|
||||
IM_BOOTSTRAP_ADMIN_PASSWORD: ${IM_BOOTSTRAP_ADMIN_PASSWORD}
|
||||
IM_BOOTSTRAP_ADMIN_REAL_NAME: ${IM_BOOTSTRAP_ADMIN_REAL_NAME}
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
volumes:
|
||||
- uploads:/data/uploads
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1:8888/healthz"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
networks: [internal]
|
||||
|
||||
edge:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deploy/Dockerfile.edge
|
||||
args:
|
||||
VITE_API_ORIGIN: https://${API_DOMAIN}
|
||||
VITE_WS_ORIGIN: wss://${API_DOMAIN}
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
api:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
APP_DOMAIN: ${APP_DOMAIN}
|
||||
ADMIN_DOMAIN: ${ADMIN_DOMAIN}
|
||||
API_DOMAIN: ${API_DOMAIN}
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./certs:/etc/nginx/certs:ro
|
||||
networks: [internal]
|
||||
|
||||
networks:
|
||||
internal:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
uploads:
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
mysql_args="--host=mysql --user=${MYSQL_USER} --database=${MYSQL_DATABASE} --default-character-set=utf8mb4 --batch --skip-column-names"
|
||||
|
||||
until MYSQL_PWD="${MYSQL_PASSWORD}" mysql ${mysql_args} --execute "SELECT 1" >/dev/null 2>&1; do
|
||||
sleep 2
|
||||
done
|
||||
|
||||
MYSQL_PWD="${MYSQL_PASSWORD}" mysql ${mysql_args} --execute "CREATE TABLE IF NOT EXISTS schema_migrations (version VARCHAR(255) NOT NULL PRIMARY KEY, checksum CHAR(64) NOT NULL, applied_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
|
||||
|
||||
for file in /migrations/*.sql; do
|
||||
version="$(basename "${file}")"
|
||||
checksum="$(sha256sum "${file}" | awk '{print $1}')"
|
||||
existing="$(MYSQL_PWD="${MYSQL_PASSWORD}" mysql ${mysql_args} --execute "SELECT checksum FROM schema_migrations WHERE version='${version}'" 2>/dev/null || true)"
|
||||
if [ -n "${existing}" ]; then
|
||||
if [ "${existing}" != "${checksum}" ]; then
|
||||
echo "Migration checksum mismatch: ${version}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Already applied: ${version}"
|
||||
continue
|
||||
fi
|
||||
echo "Applying: ${version}"
|
||||
MYSQL_PWD="${MYSQL_PASSWORD}" mysql ${mysql_args} < "${file}"
|
||||
MYSQL_PWD="${MYSQL_PASSWORD}" mysql ${mysql_args} --execute "INSERT INTO schema_migrations(version,checksum) VALUES('${version}','${checksum}')"
|
||||
done
|
||||
@@ -0,0 +1,85 @@
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name ${APP_DOMAIN} ${ADMIN_DOMAIN} ${API_DOMAIN};
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name ${APP_DOMAIN};
|
||||
ssl_certificate /etc/nginx/certs/server.crt;
|
||||
ssl_certificate_key /etc/nginx/certs/server.key;
|
||||
root /srv/mobile;
|
||||
index index.html;
|
||||
server_tokens off;
|
||||
add_header X-Content-Type-Options nosniff always;
|
||||
add_header Referrer-Policy strict-origin-when-cross-origin always;
|
||||
add_header Permissions-Policy "camera=(self), microphone=(self), geolocation=(self)" always;
|
||||
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: blob: https:; media-src 'self' blob: https:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; connect-src 'self' https://${API_DOMAIN} wss://${API_DOMAIN}; frame-src https:;" always;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
location ~* \.(?:js|css|png|jpg|jpeg|gif|webp|svg|ico|woff2?)$ {
|
||||
expires 7d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
try_files $uri =404;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name ${ADMIN_DOMAIN};
|
||||
ssl_certificate /etc/nginx/certs/server.crt;
|
||||
ssl_certificate_key /etc/nginx/certs/server.key;
|
||||
root /srv/admin;
|
||||
index index.html;
|
||||
server_tokens off;
|
||||
add_header X-Content-Type-Options nosniff always;
|
||||
add_header Referrer-Policy no-referrer always;
|
||||
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
||||
add_header X-Frame-Options DENY always;
|
||||
|
||||
location /admin/v1/ {
|
||||
proxy_pass http://api:8888;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
}
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name ${API_DOMAIN};
|
||||
ssl_certificate /etc/nginx/certs/server.crt;
|
||||
ssl_certificate_key /etc/nginx/certs/server.key;
|
||||
server_tokens off;
|
||||
client_max_body_size 16m;
|
||||
|
||||
location /ws {
|
||||
proxy_pass http://api:8888;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_read_timeout 70s;
|
||||
}
|
||||
location / {
|
||||
proxy_pass http://api:8888;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user