Browse Source

上传服务端代码

main
liwei1dao 2 months ago
parent
commit
a36f0a016a
  1. 6
      CLAUDE.md
  2. 9
      apps/services/sys/nats/core.go
  3. 24
      apps/services/sys/nats/nats.go
  4. 18
      deploy/admin/README.md
  5. 11
      deploy/admin/prod-deploy.sh
  6. 10
      deploy/console/README.md
  7. 11
      deploy/console/prod-deploy.sh
  8. 11
      deploy/registry-profiles.sh

6
CLAUDE.md

@ -72,10 +72,12 @@ cd deploy/console && ./prod-deploy.sh 0.2.1 yunyan # 第 2 个参数选生产
- `lingpu` → `registry.lingpu.net`(灵谱香港机) - `lingpu` → `registry.lingpu.net`(灵谱香港机)
**生产镜像名也按公司区分**,映射同在 `registry-profiles.sh` 的 `prod_image_name()`(build 与 deploy 共用一份,防漂移): **生产镜像名也按公司区分**,映射同在 `registry-profiles.sh` 的 `prod_image_name()`(build 与 deploy 共用一份,防漂移):
云雁沿用历史名 `yunyan-admin`/`console-a11`/`voitrans-a11`;灵谱用 `starpivot-*` 新名。 admin/console 两家都用 `starpivot-*`(与 `docker-compose.yml` 里写死的 image/container_name 一致);
只有云雁的 app 仍是 `voitrans-a11`(上海机正跑着 7 个该镜像的容器,改名 compose 会认不出)。
admin/console 的 `prod-deploy.sh` 已改为**多生产环境档案**(`env_profile()`,同 `dev-deploy.sh` 的写法): admin/console 的 `prod-deploy.sh` 已改为**多生产环境档案**(`env_profile()`,同 `dev-deploy.sh` 的写法):
- `yunyan` → 47.253.88.66(美国阿里云),目录 `/home/work/yunyan-admin`、`/home/work/console-a11` - `yunyan` → 139.224.212.237(上海阿里云,与业务后端 app 同机),目录 `/home/work/starpivot/{admin,console}`,密钥 `~/Documents/key/aliyun_shanghai.pem`
(2026-08-05 由美国机 47.253.88.66 迁来;美国机上的 `yunyan-admin`/`console-a11` 容器仍在跑,待验证后停,脚本已不再指向它)
- `lingpu` → 8.166.114.65(广州阿里云),目录 `/home/work/starpivot/{admin,console}`,密钥 `~/.ssh/lingpu-gz.pem` - `lingpu` → 8.166.114.65(广州阿里云),目录 `/home/work/starpivot/{admin,console}`,密钥 `~/.ssh/lingpu-gz.pem`
`dev-deploy.sh <环境>` 只做镜像部署:构建推送 → 远端 pull 重启,**不上传** 服务器上的真实 `.env` / `confs/*.yaml`(只下发 `*.example` 模板)。 `dev-deploy.sh <环境>` 只做镜像部署:构建推送 → 远端 pull 重启,**不上传** 服务器上的真实 `.env` / `confs/*.yaml`(只下发 `*.example` 模板)。

9
apps/services/sys/nats/core.go

@ -30,7 +30,14 @@ func OnInit(config map[string]interface{}, opt ...Option) (err error) {
if options, err = newOptions(config, opt...); err != nil { if options, err = newOptions(config, opt...); err != nil {
return return
} }
defsys, err = newSys(options) // 注意:必须先取到具体类型再判空后赋值。若直接 defsys, err = newSys(...),
// 失败时会把 (*natsSys)(nil) 装进接口,使 defsys != nil 成立,
// 后续 Conn()/JetStream() 的 nil 判断全部失效并在方法内 panic。
var sys *natsSys
if sys, err = newSys(options); err != nil {
return
}
defsys = sys
return return
} }

24
apps/services/sys/nats/nats.go

@ -43,22 +43,40 @@ func (this *natsSys) init() (err error) {
return return
} }
func (this *natsSys) Conn() *gonats.Conn { return this.conn } // 以下方法一律做 nil 接收者防护:NewSys 失败时调用方若把 (nil, err) 存进 ISys 接口,
// 接口不为 nil 但接收者为 nil,读字段会直接 panic。宁可降级返回 nil / ErrNotReady。
func (this *natsSys) Conn() *gonats.Conn {
if this == nil {
return nil
}
return this.conn
}
func (this *natsSys) JetStream() gonats.JetStreamContext { return this.js } func (this *natsSys) JetStream() gonats.JetStreamContext {
if this == nil {
return nil
}
return this.js
}
func (this *natsSys) Publish(subject string, data []byte) error { func (this *natsSys) Publish(subject string, data []byte) error {
if this == nil || this.js == nil {
return ErrNotReady
}
_, err := this.js.Publish(subject, data) _, err := this.js.Publish(subject, data)
return err return err
} }
func (this *natsSys) PublishAsync(subject string, data []byte) error { func (this *natsSys) PublishAsync(subject string, data []byte) error {
if this == nil || this.js == nil {
return ErrNotReady
}
_, err := this.js.PublishAsync(subject, data) _, err := this.js.PublishAsync(subject, data)
return err return err
} }
func (this *natsSys) Close() { func (this *natsSys) Close() {
if this.conn != nil { if this != nil && this.conn != nil {
_ = this.conn.Drain() _ = this.conn.Drain()
} }
} }

18
deploy/admin/README.md

@ -1,8 +1,8 @@
# admin 管理前端部署 # admin 管理前端部署
把 Nuxt 4 SSR 管理后台 **admin** 打成镜像 `yunyan-admin`,推送到私有仓库 把 Nuxt 4 SSR 管理后台 **admin** 打成镜像 `starpivot-admin`,推送到私有仓库
`docker-registry.ideapsound.com`,再到目标机拉取重启。它本身不连数据库,只通过 `docker-registry.ideapsound.com`,再到目标机拉取重启。它本身不连数据库,只通过
`CONSOLE_BACKEND` 把 `/web/api`、`/console/api` 代理到同网络的 **console** 后端(容器名 `console-a11`)。 `CONSOLE_BACKEND` 把 `/web/api`、`/console/api` 代理到同网络的 **console** 后端(容器名 `starpivot-console`)。
> 约定(与 console 一致):仓库**只提交模板**(`*.example`)。部署脚本会把**模板 + `docker-compose.yml`** 同步到服务器, > 约定(与 console 一致):仓库**只提交模板**(`*.example`)。部署脚本会把**模板 + `docker-compose.yml`** 同步到服务器,
> 但**绝不上传/覆盖**你在服务器上填好的真实 `.env`(首次需登录服务器照模板复制并填值)。admin 没有 yaml 配置,全靠 `.env`。 > 但**绝不上传/覆盖**你在服务器上填好的真实 `.env`(首次需登录服务器照模板复制并填值)。admin 没有 yaml 配置,全靠 `.env`。
@ -12,7 +12,7 @@
| 文件 | 说明 | | 文件 | 说明 |
|---|---| |---|---|
| `../../apps/admin/Dockerfile` | 镜像构建(Nuxt SSR:pnpm build → node server),构建上下文 = `apps/admin/` | | `../../apps/admin/Dockerfile` | 镜像构建(Nuxt SSR:pnpm build → node server),构建上下文 = `apps/admin/` |
| `docker-compose.yml` | 运行镜像 `yunyan-admin`,`.env` 注入容器,只 `expose 3000`(经反代对外) | | `docker-compose.yml` | 运行镜像 `starpivot-admin`,`.env` 注入容器,只 `expose 3000`(经反代对外) |
| `env/env.example` | 运行时环境**模板**(只一份,不分环境);真实 `.env` 被 gitignore | | `env/env.example` | 运行时环境**模板**(只一份,不分环境);真实 `.env` 被 gitignore |
| `dev-deploy.sh` | **开发**:构建 dev 镜像 → 推送 → 远端拉取重启(固定 tag `dev-latest`) | | `dev-deploy.sh` | **开发**:构建 dev 镜像 → 推送 → 远端拉取重启(固定 tag `dev-latest`) |
| `prod-build.sh` | **生产①**:构建带版本号镜像并推送(`:版本` + `:latest`),不部署 | | `prod-build.sh` | **生产①**:构建带版本号镜像并推送(`:版本` + `:latest`),不部署 |
@ -27,7 +27,7 @@
```bash ```bash
cd deploy/admin cd deploy/admin
cp env/env.example .env # 填真实值(至少 CONSOLE_BACKEND) cp env/env.example .env # 填真实值(至少 CONSOLE_BACKEND)
docker compose up -d # 本机起 yunyan-admin docker compose up -d # 本机起 starpivot-admin
``` ```
### 服务器准备(dev / prod,一次性) ### 服务器准备(dev / prod,一次性)
@ -36,7 +36,7 @@ docker compose up -d # 本机起 yunyan-admin
(`cp env.example .env`,dev-deploy 缺真实文件时也会打印该命令): (`cp env.example .env`,dev-deploy 缺真实文件时也会打印该命令):
``` ```
<部署目录>/ # dev / prod 均默认 /home/work/yunyan-admin <部署目录>/ # dev / prod 均默认 /home/work/starpivot/admin
├── docker-compose.yml # 从本目录拷贝 ├── docker-compose.yml # 从本目录拷贝
└── .env # 照 env.example 填(CONSOLE_BACKEND / TAG / DOCKER_NETWORK) └── .env # 照 env.example 填(CONSOLE_BACKEND / TAG / DOCKER_NETWORK)
``` ```
@ -48,7 +48,7 @@ cd deploy/admin
./dev-deploy.sh lingpu # 开发:发布到灵谱测试环境(构建 dev-latest → 推送 → 远端拉取重启) ./dev-deploy.sh lingpu # 开发:发布到灵谱测试环境(构建 dev-latest → 推送 → 远端拉取重启)
./dev-deploy.sh yunyan # 开发:发布到云雁测试环境 ./dev-deploy.sh yunyan # 开发:发布到云雁测试环境
./dev-deploy.sh # 不带参数 → 交互选择测试环境 ./dev-deploy.sh # 不带参数 → 交互选择测试环境
./prod-build.sh 0.1.1 # 生产①:构建并推送 yunyan-admin:0.1.1 (+latest) ./prod-build.sh 0.1.1 yunyan # 生产①:构建并推送 starpivot-admin:0.1.1 (+latest);第2参数选公司
./prod-deploy.sh 0.1.1 lingpu # 生产②:部署 0.1.1 到指定生产环境(yunyan|lingpu),省略环境则交互选 ./prod-deploy.sh 0.1.1 lingpu # 生产②:部署 0.1.1 到指定生产环境(yunyan|lingpu),省略环境则交互选
./prod-deploy.sh rollback lingpu # 生产回滚到上一版本(与当前版本来回切换) ./prod-deploy.sh rollback lingpu # 生产回滚到上一版本(与当前版本来回切换)
``` ```
@ -61,11 +61,11 @@ cd deploy/admin
- **部署目标与凭据**:写死在各脚本顶部。`dev-deploy.sh` 的 `env_profile()`(每个测试环境一套 `REGISTRY/DEV_HOST/DEV_KEY/DEV_DIR`,现有 `lingpu`、`yunyan`;新增环境加一段 case 并写进 `ENVS`)、`prod-deploy.sh` 的 `env_profile()`(每个生产环境一套 `REGISTRY/IMAGE_NAME/DEPLOY_HOST/DEPLOY_KEY/DEPLOY_DIR`,现有 `yunyan`、`lingpu`;新增环境加一段 case 并写进 `PROD_ENVS`); - **部署目标与凭据**:写死在各脚本顶部。`dev-deploy.sh` 的 `env_profile()`(每个测试环境一套 `REGISTRY/DEV_HOST/DEV_KEY/DEV_DIR`,现有 `lingpu`、`yunyan`;新增环境加一段 case 并写进 `ENVS`)、`prod-deploy.sh` 的 `env_profile()`(每个生产环境一套 `REGISTRY/IMAGE_NAME/DEPLOY_HOST/DEPLOY_KEY/DEPLOY_DIR`,现有 `yunyan`、`lingpu`;新增环境加一段 case 并写进 `PROD_ENVS`);
仓库账号 `admin` / 密码已写死;`DEPLOY_KEY` 默认 `/Users/liwei/Documents/key/yunyan-us.pem`(prod)。 仓库账号 `admin` / 密码已写死;`DEPLOY_KEY` 默认 `/Users/liwei/Documents/key/yunyan-us.pem`(prod)。
- ⚠️ 本目录脚本纳入 git,**公开仓库前务必清空** `*_HOST` 与 `REGISTRY_PASS`。 - ⚠️ 本目录脚本纳入 git,**公开仓库前务必清空** `*_HOST` 与 `REGISTRY_PASS`。
- **后端地址**:`.env` 的 `CONSOLE_BACKEND`,须指向同网络的 console 容器,默认 `http://console-a11:8080`。 - **后端地址**:`.env` 的 `CONSOLE_BACKEND`,须指向同网络的 console 容器,默认 `http://starpivot-console:8080`。
## 端口与反代 ## 端口与反代
admin HTTP `3000`(容器内,只 `expose` 不 publish)。须由 **1panel 反代 / NPM** 把后台域名 admin HTTP `3000`(容器内,只 `expose` 不 publish)。须由 **1panel 反代 / NPM** 把后台域名
(如 `console.yunyanservice.xyz`)转发到 `yunyan-admin:3000`。admin 再在内部把 `/web/api`、`/console/api` (如 `console.yunyanservice.xyz`)转发到 `starpivot-admin:3000`。admin 再在内部把 `/web/api`、`/console/api`
代理到 `console-a11:8080`,所以**单域名**即可跑通整套(浏览器 → 反代 → yunyan-admin → console-a11)。 代理到 `starpivot-console:8080`,所以**单域名**即可跑通整套(浏览器 → 反代 → starpivot-admin → starpivot-console)。
admin 与 console 须在同一 `DOCKER_NETWORK`(默认 `1panel-network`)。 admin 与 console 须在同一 `DOCKER_NETWORK`(默认 `1panel-network`)。

11
deploy/admin/prod-deploy.sh

@ -40,11 +40,14 @@ env_profile() {
DEPLOY_USER=root DEPLOY_USER=root
case "$DEPLOY_ENV" in case "$DEPLOY_ENV" in
yunyan) yunyan)
ENV_DESC="云雁生产环境 · 美国阿里云" # 2026-08-05 起 admin 生产目标改为上海机(与业务后端 app 同机)。
# 旧目标:美国机 47.253.88.66,其上 yunyan-admin:0.1.1 容器仍在跑,待新环境验证后再停;
# 需要临时回连那台时,把下面三行换成 47.253.88.66 / yunyan-us.pem / /home/work/yunyan-admin。
ENV_DESC="云雁生产环境 · 上海阿里云"
registry_profile yunyan || return 1 # 镜像仓库:云雁 registry_profile yunyan || return 1 # 镜像仓库:云雁
DEPLOY_HOST="47.253.88.66" # ← 云雁生产服务器 IP DEPLOY_HOST="139.224.212.237" # ← 云雁生产服务器 IP(上海)
DEPLOY_KEY=/Users/liwei/Documents/key/yunyan-us.pem DEPLOY_KEY=/Users/liwei/Documents/key/aliyun_shanghai.pem
DEPLOY_DIR="/home/work/yunyan-admin" # 远端部署目录(其中 docker-compose.yml/.env 由你自己维护) DEPLOY_DIR="/home/work/starpivot/admin" # 远端部署目录(其中 docker-compose.yml/.env 由你自己维护)
;; ;;
lingpu) lingpu)
ENV_DESC="灵谱生产环境 · 广州阿里云 (lingpu-gz)" ENV_DESC="灵谱生产环境 · 广州阿里云 (lingpu-gz)"

10
deploy/console/README.md

@ -1,6 +1,6 @@
# console 管理后台部署 # console 管理后台部署
把单服务 **console**(lego single 服务,维护 app_registry、跨应用统计)打成镜像 `console-a11`, 把单服务 **console**(lego single 服务,维护 app_registry、跨应用统计)打成镜像 `starpivot-console`,
推送到私有仓库 `docker-registry.ideapsound.com`,再到目标机拉取重启。依赖 PostgreSQL / Redis / NATS 推送到私有仓库 `docker-registry.ideapsound.com`,再到目标机拉取重启。依赖 PostgreSQL / Redis / NATS
由目标环境共享提供(在 `.env` 里填地址/容器名),不在此 compose 内搭建。 由目标环境共享提供(在 `.env` 里填地址/容器名),不在此 compose 内搭建。
@ -13,7 +13,7 @@
|---|---| |---|---|
| `../../apps/services/Dockerfile.console` | 镜像构建(编译 console 二进制 + entrypoint + 内置 consoleweb 静态),构建上下文 = `apps/services/` | | `../../apps/services/Dockerfile.console` | 镜像构建(编译 console 二进制 + entrypoint + 内置 consoleweb 静态),构建上下文 = `apps/services/` |
| `../../apps/services/entrypoint.console.sh` | 容器入口 | | `../../apps/services/entrypoint.console.sh` | 容器入口 |
| `docker-compose.yml` | 运行镜像 `console-a11`,挂 `./confs`、`./log`,`.env` 注入容器(此文件由你放到服务器) | | `docker-compose.yml` | 运行镜像 `starpivot-console`,挂 `./confs`、`./log`,`.env` 注入容器(此文件由你放到服务器) |
| `confs/console.yaml.example` | console 配置**模板**;真实 `console.yaml` 被 gitignore,密钥/端点用 `${VAR}` 由 `.env` 注入 | | `confs/console.yaml.example` | console 配置**模板**;真实 `console.yaml` 被 gitignore,密钥/端点用 `${VAR}` 由 `.env` 注入 |
| `env/env.example` | 运行时环境**模板**(只一份,不分环境);真实 `.env` 被 gitignore | | `env/env.example` | 运行时环境**模板**(只一份,不分环境);真实 `.env` 被 gitignore |
| `dev-deploy.sh` | **开发**:构建 dev 镜像 → 推送 → 远端拉取重启(固定 tag `dev-latest`) | | `dev-deploy.sh` | **开发**:构建 dev 镜像 → 推送 → 远端拉取重启(固定 tag `dev-latest`) |
@ -30,7 +30,7 @@
cd deploy/console cd deploy/console
cp env/env.example .env # 填真实值 cp env/env.example .env # 填真实值
cp confs/console.yaml.example confs/console.yaml cp confs/console.yaml.example confs/console.yaml
docker compose --env-file .env up -d # 本机起 console-a11 docker compose --env-file .env up -d # 本机起 starpivot-console
``` ```
### 服务器准备(dev / prod,一次性) ### 服务器准备(dev / prod,一次性)
@ -39,7 +39,7 @@ docker compose --env-file .env up -d # 本机起 console-a11
(`cp env.example .env`、`cp confs/console.yaml.example confs/console.yaml`,dev-deploy 缺真实文件时也会打印这两条命令): (`cp env.example .env`、`cp confs/console.yaml.example confs/console.yaml`,dev-deploy 缺真实文件时也会打印这两条命令):
``` ```
<部署目录>/ # dev 默认 /home/work/console-a11,prod 默认 /opt/yunyan/console <部署目录>/ # dev / prod 均默认 /home/work/starpivot/console
├── docker-compose.yml # 从本目录拷贝 ├── docker-compose.yml # 从本目录拷贝
├── .env # 照 env/env.example 填(POSTGRES_DSN/REDIS_*/NATS_URL/ ├── .env # 照 env/env.example 填(POSTGRES_DSN/REDIS_*/NATS_URL/
│ # CONSOLE_TOKEN_KEY/CONSOLE_ADMIN_*/FIELD_ENCRYPT_KEY 等) │ # CONSOLE_TOKEN_KEY/CONSOLE_ADMIN_*/FIELD_ENCRYPT_KEY 等)
@ -54,7 +54,7 @@ cd deploy/console
./dev-deploy.sh lingpu # 开发:发布到灵谱测试环境(构建 dev-latest → 推送 → 远端拉取重启) ./dev-deploy.sh lingpu # 开发:发布到灵谱测试环境(构建 dev-latest → 推送 → 远端拉取重启)
./dev-deploy.sh yunyan # 开发:发布到云雁测试环境 ./dev-deploy.sh yunyan # 开发:发布到云雁测试环境
./dev-deploy.sh # 不带参数 → 交互选择测试环境 ./dev-deploy.sh # 不带参数 → 交互选择测试环境
./prod-build.sh 0.1.1 # 生产①:构建并推送 console-a11:0.1.1 (+latest) ./prod-build.sh 0.1.1 yunyan # 生产①:构建并推送 starpivot-console:0.1.1 (+latest);第2参数选公司
./prod-deploy.sh 0.1.1 lingpu # 生产②:部署 0.1.1 到指定生产环境(yunyan|lingpu),省略环境则交互选 ./prod-deploy.sh 0.1.1 lingpu # 生产②:部署 0.1.1 到指定生产环境(yunyan|lingpu),省略环境则交互选
./prod-deploy.sh rollback lingpu # 生产回滚到上一版本(与当前版本来回切换) ./prod-deploy.sh rollback lingpu # 生产回滚到上一版本(与当前版本来回切换)
``` ```

11
deploy/console/prod-deploy.sh

@ -40,11 +40,14 @@ env_profile() {
DEPLOY_USER=root DEPLOY_USER=root
case "$DEPLOY_ENV" in case "$DEPLOY_ENV" in
yunyan) yunyan)
ENV_DESC="云雁生产环境 · 美国阿里云" # 2026-08-05 起 console 生产目标改为上海机(与业务后端 app 同机)。
# 旧目标:美国机 47.253.88.66,其上 console-a11:0.1.1 容器仍在跑,待新环境验证后再停;
# 需要临时回连那台时,把下面三行换成 47.253.88.66 / yunyan-us.pem / /home/work/console-a11。
ENV_DESC="云雁生产环境 · 上海阿里云"
registry_profile yunyan || return 1 # 镜像仓库:云雁 registry_profile yunyan || return 1 # 镜像仓库:云雁
DEPLOY_HOST="47.253.88.66" # ← 云雁生产服务器 IP DEPLOY_HOST="139.224.212.237" # ← 云雁生产服务器 IP(上海)
DEPLOY_KEY=/Users/liwei/Documents/key/yunyan-us.pem DEPLOY_KEY=/Users/liwei/Documents/key/aliyun_shanghai.pem
DEPLOY_DIR="/home/work/console-a11" # 远端部署目录(其中 docker-compose.yml/.env/confs 由你自己维护) DEPLOY_DIR="/home/work/starpivot/console" # 远端部署目录(其中 docker-compose.yml/.env/confs 由你自己维护)
;; ;;
lingpu) lingpu)
ENV_DESC="灵谱生产环境 · 广州阿里云 (lingpu-gz)" ENV_DESC="灵谱生产环境 · 广州阿里云 (lingpu-gz)"

11
deploy/registry-profiles.sh

@ -41,15 +41,16 @@ registry_profile() {
# 生产镜像名按公司区分。prod-build.sh 与 prod-deploy.sh 都调它,避免两处各写一份映射后漂移 # 生产镜像名按公司区分。prod-build.sh 与 prod-deploy.sh 都调它,避免两处各写一份映射后漂移
# ——构建推的名字和部署拉的名字对不上,报错要到远端 docker pull 才暴露。 # ——构建推的名字和部署拉的名字对不上,报错要到远端 docker pull 才暴露。
# #
# 云雁:沿用历史命名(yunyan-admin / console-a11 / voitrans-a11)。服务器上正跑着这些同名容器, # admin/console:两家都用 starpivot-* 新命名,与 docker-compose.yml 里写死的 image/container_name
# 改名会让 compose 认不出既有容器,故不动它。 # 一致。云雁的 admin/console 生产目标已于 2026-08-05 迁到上海机(那台没有历史容器),
# 灵谱:全新部署,用 starpivot-* 新命名,与灵谱测试环境(dev-deploy.sh)保持一致。 # 旧名 yunyan-admin / console-a11 只留在待下线的美国机 47.253.88.66 上,不再由脚本部署。
# app:云雁仍用 voitrans-a11——上海机正跑着 7 个该镜像的容器,改名会让 compose 认不出既有容器。
# #
# 用法:IMAGE_NAME="$(prod_image_name lingpu admin)" # 用法:IMAGE_NAME="$(prod_image_name lingpu admin)"
prod_image_name() { # $1=公司 $2=服务(admin|console|app) prod_image_name() { # $1=公司 $2=服务(admin|console|app)
case "${1:-}/${2:-}" in case "${1:-}/${2:-}" in
yunyan/admin) printf 'yunyan-admin' ;; yunyan/admin) printf 'starpivot-admin' ;;
yunyan/console) printf 'console-a11' ;; yunyan/console) printf 'starpivot-console' ;;
yunyan/app) printf 'voitrans-a11' ;; yunyan/app) printf 'voitrans-a11' ;;
lingpu/admin|lingpu/console|lingpu/app) printf 'starpivot-%s' "$2" ;; lingpu/admin|lingpu/console|lingpu/app) printf 'starpivot-%s' "$2" ;;
*) *)

Loading…
Cancel
Save