fix: polish channel groups and add production deployment

This commit is contained in:
hectorzhao
2026-07-07 11:16:08 +08:00
parent b5132d7f4e
commit 72f2c010ce
44 changed files with 1265 additions and 489 deletions
+75
View File
@@ -0,0 +1,75 @@
import { createHash, randomBytes } from 'node:crypto';
import { writeFileSync } from 'node:fs';
import { PrismaPg } from '../../api/node_modules/@prisma/adapter-pg/dist/index.js';
import { PrismaClient } from '../../api/node_modules/@prisma/client/index.js';
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
throw new Error('DATABASE_URL is required');
}
const prisma = new PrismaClient({ adapter: new PrismaPg(databaseUrl) });
const username = process.env.PROD_ADMIN_USERNAME || 'prod_admin';
const email = process.env.PROD_ADMIN_EMAIL || 'admin@example.com';
const password = process.env.PROD_ADMIN_PASSWORD || randomBytes(18).toString('base64url');
const credentialFile = process.env.PROD_ADMIN_CREDENTIAL_FILE;
function hashPassword(value) {
return createHash('sha256').update(value).digest('hex');
}
async function main() {
const role = await prisma.role.upsert({
where: { code: 'platform_admin' },
update: { name: '平台管理员', scope: 'platform' },
create: { code: 'platform_admin', name: '平台管理员', scope: 'platform' },
});
const user = await prisma.user.upsert({
where: { username },
update: {
email,
displayName: '生产平台管理员',
passwordHash: hashPassword(password),
status: 'active',
failedLoginCount: 0,
lockedUntil: null,
deletedAt: null,
tenantId: null,
},
create: {
username,
email,
displayName: '生产平台管理员',
passwordHash: hashPassword(password),
status: 'active',
},
});
await prisma.userRole.upsert({
where: { userId_roleId: { userId: user.id, roleId: role.id } },
update: {},
create: { userId: user.id, roleId: role.id },
});
const message = [
'CMPP production admin account',
`username=${username}`,
`email=${email}`,
`password=${password}`,
`generatedAt=${new Date().toISOString()}`,
'',
].join('\n');
if (credentialFile) {
writeFileSync(credentialFile, message, { mode: 0o600 });
}
console.log(`Production admin is ready: ${email}`);
if (!credentialFile) {
console.log(`Temporary password: ${password}`);
}
}
main()
.finally(() => prisma.$disconnect())
.catch((error) => {
console.error(error);
process.exit(1);
});
+255
View File
@@ -0,0 +1,255 @@
#!/usr/bin/env bash
set -Eeuo pipefail
APP_DIR="${APP_DIR:-/opt/cmpp-platform}"
REPO_URL="${REPO_URL:-http://175.27.255.91:3000/hectorzhao/lislgosms.git}"
BRANCH="${BRANCH:-main}"
PUBLIC_HTTP_PORT="${PUBLIC_HTTP_PORT:-12026}"
API_PORT="${API_PORT:-3000}"
GATEWAY_CONTROL_ADDR="${GATEWAY_CONTROL_ADDR:-127.0.0.1:8090}"
GATEWAY_CMPP_ADDR="${GATEWAY_CMPP_ADDR:-0.0.0.0:17890}"
DB_NAME="${DB_NAME:-cmpp_platform}"
DB_USER="${DB_USER:-cmpp}"
DB_PASSWORD="${DB_PASSWORD:-$(openssl rand -base64 24 | tr -d '\n')}"
MINIO_ROOT_USER="${MINIO_ROOT_USER:-cmpp_minio}"
MINIO_ROOT_PASSWORD="${MINIO_ROOT_PASSWORD:-$(openssl rand -base64 32 | tr -d '\n')}"
MINIO_BUCKET="${MINIO_BUCKET:-cmpp-platform}"
PROD_ADMIN_EMAIL="${PROD_ADMIN_EMAIL:-admin@example.com}"
PROD_ADMIN_USERNAME="${PROD_ADMIN_USERNAME:-prod_admin}"
PROD_ADMIN_PASSWORD="${PROD_ADMIN_PASSWORD:-$(openssl rand -base64 18 | tr -d '\n')}"
if [[ "$(id -u)" -ne 0 ]]; then
echo "Run as root." >&2
exit 1
fi
log() { printf '\n[%s] %s\n' "$(date '+%F %T')" "$*"; }
install_packages() {
log "Installing OS packages"
if command -v apt-get >/dev/null 2>&1; then
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl gnupg git nginx redis-server postgresql postgresql-contrib build-essential tar gzip openssl
if ! command -v node >/dev/null 2>&1 || [[ "$(node -v | sed 's/^v//' | cut -d. -f1)" -lt 22 ]]; then
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
DEBIAN_FRONTEND=noninteractive apt-get install -y nodejs
fi
elif command -v dnf >/dev/null 2>&1; then
dnf install -y ca-certificates curl git nginx redis postgresql-server postgresql-contrib gcc gcc-c++ make tar gzip openssl
if [[ ! -d /var/lib/pgsql/data/base ]]; then
postgresql-setup --initdb
fi
if ! command -v node >/dev/null 2>&1 || [[ "$(node -v | sed 's/^v//' | cut -d. -f1)" -lt 22 ]]; then
curl -fsSL https://rpm.nodesource.com/setup_22.x | bash -
dnf install -y nodejs
fi
elif command -v yum >/dev/null 2>&1; then
yum install -y ca-certificates curl git nginx redis postgresql-server postgresql-contrib gcc gcc-c++ make tar gzip openssl
if [[ ! -d /var/lib/pgsql/data/base ]]; then
postgresql-setup initdb
fi
if ! command -v node >/dev/null 2>&1 || [[ "$(node -v | sed 's/^v//' | cut -d. -f1)" -lt 22 ]]; then
curl -fsSL https://rpm.nodesource.com/setup_22.x | bash -
yum install -y nodejs
fi
else
echo "Unsupported Linux distribution: apt-get/dnf/yum not found." >&2
exit 1
fi
}
install_go() {
local version="${GO_VERSION:-1.26.0}"
if command -v go >/dev/null 2>&1 && [[ "$(go version | awk '{print $3}' | sed 's/go//')" == "$version" ]]; then
return
fi
log "Installing Go ${version}"
curl -fL "https://go.dev/dl/go${version}.linux-amd64.tar.gz" -o /tmp/go.tar.gz
rm -rf /usr/local/go
tar -C /usr/local -xzf /tmp/go.tar.gz
ln -sf /usr/local/go/bin/go /usr/local/bin/go
}
install_minio() {
log "Installing MinIO"
curl -fL https://dl.min.io/server/minio/release/linux-amd64/minio -o /usr/local/bin/minio
chmod +x /usr/local/bin/minio
useradd --system --home /var/lib/minio --shell /usr/sbin/nologin minio 2>/dev/null || true
mkdir -p /var/lib/minio
chown -R minio:minio /var/lib/minio
}
start_infra() {
log "Starting PostgreSQL and Redis"
systemctl enable --now postgresql || systemctl enable --now postgresql.service
systemctl enable --now redis-server 2>/dev/null || systemctl enable --now redis
runuser -u postgres -- psql <<SQL
DO \$\$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '${DB_USER}') THEN
CREATE ROLE ${DB_USER} LOGIN PASSWORD '${DB_PASSWORD}';
ELSE
ALTER ROLE ${DB_USER} WITH LOGIN PASSWORD '${DB_PASSWORD}';
END IF;
END
\$\$;
SELECT 'CREATE DATABASE ${DB_NAME} OWNER ${DB_USER}'
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${DB_NAME}')\\gexec
ALTER DATABASE ${DB_NAME} OWNER TO ${DB_USER};
SQL
}
write_env() {
log "Writing production environment"
mkdir -p /etc/cmpp-platform "$APP_DIR" "$APP_DIR/logs/api" "$APP_DIR/logs/gateway" "$APP_DIR/backups"
cat >/etc/cmpp-platform/cmpp-platform.env <<EOF
NODE_ENV=production
API_PORT=${API_PORT}
DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@127.0.0.1:5432/${DB_NAME}?schema=public
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
MINIO_ENDPOINT=127.0.0.1:9000
MINIO_ACCESS_KEY=${MINIO_ROOT_USER}
MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD}
MINIO_BUCKET=${MINIO_BUCKET}
OBJECT_STORAGE_DRIVER=minio
GATEWAY_CONTROL_URL=http://127.0.0.1:8090
GATEWAY_HEALTH_ADDR=${GATEWAY_CONTROL_ADDR}
GATEWAY_CMPP_ADDR=${GATEWAY_CMPP_ADDR}
API_BASE_URL=http://127.0.0.1:${API_PORT}/api
EOF
chmod 600 /etc/cmpp-platform/cmpp-platform.env
cat >/etc/cmpp-platform/minio.env <<EOF
MINIO_ROOT_USER=${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
EOF
chmod 600 /etc/cmpp-platform/minio.env
}
write_services() {
log "Writing systemd and nginx configuration"
cat >/etc/systemd/system/cmpp-minio.service <<'EOF'
[Unit]
Description=CMPP MinIO object storage
After=network.target
[Service]
User=minio
Group=minio
EnvironmentFile=/etc/cmpp-platform/minio.env
ExecStart=/usr/local/bin/minio server /var/lib/minio --address 127.0.0.1:9000 --console-address 127.0.0.1:9001
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
cat >/etc/systemd/system/cmpp-api.service <<EOF
[Unit]
Description=CMPP Platform API
After=network.target postgresql.service redis.service cmpp-minio.service
[Service]
WorkingDirectory=${APP_DIR}/api
EnvironmentFile=/etc/cmpp-platform/cmpp-platform.env
ExecStart=/usr/bin/node dist/main.js
Restart=always
RestartSec=5
StandardOutput=append:${APP_DIR}/logs/api/stdout.log
StandardError=append:${APP_DIR}/logs/api/stderr.log
[Install]
WantedBy=multi-user.target
EOF
cat >/etc/systemd/system/cmpp-gateway.service <<EOF
[Unit]
Description=CMPP Gateway control service
After=network.target cmpp-api.service
[Service]
WorkingDirectory=${APP_DIR}
EnvironmentFile=/etc/cmpp-platform/cmpp-platform.env
ExecStart=${APP_DIR}/dist/cmpp-gateway
Restart=always
RestartSec=5
StandardOutput=append:${APP_DIR}/logs/gateway/stdout.log
StandardError=append:${APP_DIR}/logs/gateway/stderr.log
[Install]
WantedBy=multi-user.target
EOF
cat >/etc/nginx/conf.d/cmpp-platform.conf <<EOF
server {
listen ${PUBLIC_HTTP_PORT};
server_name _;
root ${APP_DIR}/dist;
index index.html;
client_max_body_size 50m;
location /api/ {
proxy_pass http://127.0.0.1:${API_PORT}/api/;
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;
}
}
EOF
nginx -t
systemctl enable nginx
}
checkout_code() {
log "Checking out application code"
if [[ -d "$APP_DIR/.git" ]]; then
git -C "$APP_DIR" fetch origin "$BRANCH"
git -C "$APP_DIR" checkout "$BRANCH"
git -C "$APP_DIR" reset --hard "origin/$BRANCH"
else
rm -rf "$APP_DIR"
git clone --branch "$BRANCH" "$REPO_URL" "$APP_DIR"
fi
}
run_deploy() {
log "Building and deploying application"
PROD_ADMIN_EMAIL="$PROD_ADMIN_EMAIL" \
PROD_ADMIN_USERNAME="$PROD_ADMIN_USERNAME" \
PROD_ADMIN_PASSWORD="$PROD_ADMIN_PASSWORD" \
bash "$APP_DIR/tools/deploy/production-deploy.sh"
}
install_packages
install_go
install_minio
start_infra
write_env
write_services
checkout_code
run_deploy
cat >/root/cmpp-platform-credentials.txt <<EOF
CMPP production credentials
admin_url=http://$(hostname -I | awk '{print $1}'):${PUBLIC_HTTP_PORT}/admin/login
admin_username=${PROD_ADMIN_USERNAME}
admin_email=${PROD_ADMIN_EMAIL}
admin_password=${PROD_ADMIN_PASSWORD}
database=postgresql://${DB_USER}:***@127.0.0.1:5432/${DB_NAME}
minio_user=${MINIO_ROOT_USER}
minio_password=${MINIO_ROOT_PASSWORD}
EOF
chmod 600 /root/cmpp-platform-credentials.txt
log "Production bootstrap finished"
echo "Credentials saved to /root/cmpp-platform-credentials.txt"
echo "Frontend: http://<server-ip>:${PUBLIC_HTTP_PORT}"
echo "Gateway control health: http://127.0.0.1:8090/health"
echo "CMPP inbound port variable reserved: ${GATEWAY_CMPP_ADDR}"
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
set -Eeuo pipefail
APP_DIR="${APP_DIR:-/opt/cmpp-platform}"
ENV_FILE="${ENV_FILE:-/etc/cmpp-platform/cmpp-platform.env}"
ADMIN_CREDENTIAL_FILE="${ADMIN_CREDENTIAL_FILE:-/root/cmpp-platform-admin.txt}"
if [[ "$(id -u)" -ne 0 ]]; then
echo "Run as root." >&2
exit 1
fi
if [[ ! -f "$ENV_FILE" ]]; then
echo "Missing environment file: $ENV_FILE" >&2
exit 1
fi
set -a
source "$ENV_FILE"
set +a
cd "$APP_DIR"
echo "[deploy] Installing dependencies"
npm ci
npm --prefix api ci
echo "[deploy] Generating Prisma client and applying migrations"
npm --prefix api run prisma:generate
npm --prefix api run prisma:migrate:deploy
echo "[deploy] Building frontend, API and gateway"
npm run build
npm --prefix api run build
(cd gateway && /usr/local/bin/go build -o "$APP_DIR/dist/cmpp-gateway" ./cmd/gateway)
echo "[deploy] Ensuring production admin"
PROD_ADMIN_CREDENTIAL_FILE="$ADMIN_CREDENTIAL_FILE" node tools/deploy/ensure-production-admin.mjs
chmod 600 "$ADMIN_CREDENTIAL_FILE" || true
echo "[deploy] Restarting services"
systemctl daemon-reload
systemctl enable --now cmpp-minio cmpp-api cmpp-gateway nginx
systemctl restart cmpp-minio
systemctl restart cmpp-api
systemctl restart cmpp-gateway
systemctl restart nginx
echo "[deploy] Health checks"
sleep 3
curl -fsS "http://127.0.0.1:${API_PORT:-3000}/api/health" >/dev/null
curl -fsS "http://127.0.0.1:8090/health" >/dev/null
redis-cli -h "${REDIS_HOST:-127.0.0.1}" -p "${REDIS_PORT:-6379}" ping >/dev/null
pg_isready -d "$DATABASE_URL" >/dev/null
echo "[deploy] Done"