236 lines
6.6 KiB
Bash
236 lines
6.6 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
target=/mnt/PoolOne/apps/youyidian-notification-center
|
|
archive=/tmp/youyidian-notification-center-release.tar.gz
|
|
source_env=/mnt/PoolOne/apps/youyidian-3d/.env
|
|
backup_root=/mnt/PoolOne/apps/youyidian-notification-center-backups
|
|
|
|
test -f "$archive"
|
|
test -f "$source_env"
|
|
if tar -tzf "$archive" | grep -Eq '(^/|(^|/)\.\.(/|$))'; then
|
|
echo '安装包包含不安全路径' >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$target" "$backup_root"
|
|
if test -f "$target/package.json"; then
|
|
stamp="$(date +%Y%m%d%H%M%S)"
|
|
tar -czf "$backup_root/source-$stamp.tar.gz" --exclude=.env --exclude=data -C "$target" .
|
|
fi
|
|
tar -xzf "$archive" -C "$target"
|
|
|
|
python3 - <<'PY'
|
|
from pathlib import Path
|
|
import json
|
|
import os
|
|
import secrets
|
|
import shutil
|
|
import time
|
|
|
|
|
|
def parse_env(path: Path):
|
|
result = {}
|
|
if not path.exists():
|
|
return result
|
|
for raw in path.read_text(encoding='utf-8').splitlines():
|
|
line = raw.strip()
|
|
if not line or line.startswith('#') or '=' not in line:
|
|
continue
|
|
key, value = line.split('=', 1)
|
|
key = key.strip()
|
|
value = value.strip()
|
|
if value.startswith('"') and value.endswith('"'):
|
|
try:
|
|
value = json.loads(value)
|
|
except Exception:
|
|
value = value[1:-1]
|
|
elif value.startswith("'") and value.endswith("'"):
|
|
value = value[1:-1]
|
|
result[key] = value
|
|
return result
|
|
|
|
|
|
def quoted(value):
|
|
return json.dumps(str(value), ensure_ascii=False)
|
|
|
|
|
|
source_path = Path('/mnt/PoolOne/apps/youyidian-3d/.env')
|
|
target_path = Path('/mnt/PoolOne/apps/youyidian-notification-center/.env')
|
|
source = parse_env(source_path)
|
|
existing = parse_env(target_path)
|
|
app_key = (
|
|
source.get('DINGTALK_WORK_NOTICE_CLIENT_ID')
|
|
or source.get('DINGTALK_WORK_NOTICE_APP_KEY')
|
|
or source.get('DINGTALK_CLIENT_ID')
|
|
or source.get('DINGTALK_APP_KEY')
|
|
)
|
|
app_secret = (
|
|
source.get('DINGTALK_WORK_NOTICE_CLIENT_SECRET')
|
|
or source.get('DINGTALK_WORK_NOTICE_APP_SECRET')
|
|
or source.get('DINGTALK_CLIENT_SECRET')
|
|
or source.get('DINGTALK_APP_SECRET')
|
|
)
|
|
robot_code = source.get('DINGTALK_ROBOT_CODE')
|
|
if not app_key or not app_secret or not robot_code:
|
|
raise SystemExit('素材库生产环境缺少统一机器人配置')
|
|
|
|
try:
|
|
old_projects = json.loads(existing.get('YYD_NOTIFICATION_PROJECTS_JSON', '[]'))
|
|
except Exception:
|
|
old_projects = []
|
|
by_code = {
|
|
str(item.get('code')): item
|
|
for item in old_projects
|
|
if isinstance(item, dict)
|
|
}
|
|
|
|
|
|
def secret_for(code):
|
|
old = str(by_code.get(code, {}).get('secret') or '')
|
|
return old if len(old) >= 24 else secrets.token_hex(32)
|
|
|
|
|
|
projects = [
|
|
{
|
|
'code': 'material_library',
|
|
'name': '素材库',
|
|
'secret': secret_for('material_library'),
|
|
'allowedHosts': ['sc.jiansun.vip'],
|
|
},
|
|
{
|
|
'code': 'fixed_asset',
|
|
'name': '固定资产',
|
|
'secret': secret_for('fixed_asset'),
|
|
'allowedHosts': ['www.jsgdzc.cn', 'jsgdzc.cn'],
|
|
},
|
|
]
|
|
center_lines = [
|
|
'PORT=3088',
|
|
'DINGTALK_APP_KEY=' + quoted(app_key),
|
|
'DINGTALK_APP_SECRET=' + quoted(app_secret),
|
|
'DINGTALK_ROBOT_CODE=' + quoted(robot_code),
|
|
'YYD_NOTIFICATION_PROJECTS_JSON=' + quoted(
|
|
json.dumps(projects, ensure_ascii=False, separators=(',', ':'))
|
|
),
|
|
'WORKER_ENABLED=true',
|
|
'WORKER_INTERVAL_MS=1000',
|
|
'READ_STATUS_INTERVAL_MS=5000',
|
|
]
|
|
target_path.write_text('\n'.join(center_lines) + '\n', encoding='utf-8')
|
|
os.chmod(target_path, 0o600)
|
|
|
|
material_secret = next(
|
|
item['secret'] for item in projects if item['code'] == 'material_library'
|
|
)
|
|
updates = {
|
|
'YYD_NOTIFICATION_CENTER_URL': 'https://sc.jiansun.vip/notification-center',
|
|
'YYD_NOTIFICATION_CENTER_PROJECT_CODE': 'material_library',
|
|
'YYD_NOTIFICATION_CENTER_PROJECT_SECRET': material_secret,
|
|
'YYD_NOTIFICATION_CENTER_TEST_USER_IDS': '',
|
|
}
|
|
backup = source_path.with_name(
|
|
'.env.before-yyd-notification-' + time.strftime('%Y%m%d%H%M%S')
|
|
)
|
|
shutil.copy2(source_path, backup)
|
|
os.chmod(backup, 0o600)
|
|
lines = source_path.read_text(encoding='utf-8').splitlines()
|
|
seen = set()
|
|
output = []
|
|
for line in lines:
|
|
if '=' in line and not line.lstrip().startswith('#'):
|
|
key = line.split('=', 1)[0].strip()
|
|
if key in updates:
|
|
output.append(key + '=' + quoted(updates[key]))
|
|
seen.add(key)
|
|
continue
|
|
output.append(line)
|
|
for key, value in updates.items():
|
|
if key not in seen:
|
|
output.append(key + '=' + quoted(value))
|
|
source_path.write_text('\n'.join(output) + '\n', encoding='utf-8')
|
|
os.chmod(source_path, 0o600)
|
|
print('通知中心与素材库适配配置已写入')
|
|
PY
|
|
|
|
cd "$target"
|
|
image="youyidian-notification-center:${YYD_NOTIFICATION_IMAGE_TAG:-latest}"
|
|
if docker image inspect "$image" >/dev/null 2>&1; then
|
|
docker compose up -d --no-build
|
|
else
|
|
docker compose up -d --build
|
|
fi
|
|
|
|
healthy=0
|
|
for _ in $(seq 1 30); do
|
|
if curl -fsS --max-time 3 http://127.0.0.1:3088/health >/tmp/yyd-notification-health.json; then
|
|
healthy=1
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
if test "$healthy" != 1; then
|
|
docker compose logs --tail 120
|
|
exit 1
|
|
fi
|
|
|
|
python3 - <<'PY'
|
|
from pathlib import Path
|
|
|
|
source = Path('/etc/frp/frpc-sc-entry.toml').read_text(encoding='utf-8')
|
|
header = source.split('[[proxies]]', 1)[0]
|
|
header = header.replace(
|
|
'/root/frp/frpc-sc-entry.log',
|
|
'/root/frp/frpc-notify-entry.log',
|
|
)
|
|
proxy = '''[[proxies]]
|
|
name = "youyidian-notification-center"
|
|
type = "tcp"
|
|
localIP = "127.0.0.1"
|
|
localPort = 3088
|
|
remotePort = 2326
|
|
healthCheck.type = "http"
|
|
healthCheck.path = "/health"
|
|
healthCheck.intervalSeconds = 15
|
|
healthCheck.maxFailed = 3
|
|
healthCheck.timeoutSeconds = 5
|
|
'''
|
|
Path('/etc/frp/frpc-notify-entry.toml').write_text(
|
|
header.rstrip() + '\n\n' + proxy,
|
|
encoding='utf-8',
|
|
)
|
|
PY
|
|
chmod 600 /etc/frp/frpc-notify-entry.toml
|
|
|
|
cat > /etc/systemd/system/frpc-notify-entry.service <<'EOF'
|
|
[Unit]
|
|
Description=FRP Client for Youyidian notification center
|
|
After=network-online.target docker.service
|
|
Wants=network-online.target
|
|
Requires=docker.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
Restart=always
|
|
RestartSec=10s
|
|
LimitNOFILE=65535
|
|
ExecStart=/root/frp-upgrade/frp_0.67.0_linux_amd64/frpc -c /etc/frp/frpc-notify-entry.toml
|
|
StandardOutput=append:/root/frp/frpc-notify-entry.service.log
|
|
StandardError=append:/root/frp/frpc-notify-entry.service.log
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
systemctl daemon-reload
|
|
systemctl enable --now frpc-notify-entry.service
|
|
sleep 3
|
|
systemctl is-active --quiet frpc-notify-entry.service
|
|
|
|
rm -f "$archive"
|
|
curl -fsS http://127.0.0.1:3088/health
|
|
echo
|
|
printf 'CENTER_CONTAINER='
|
|
docker inspect -f '{{.State.Status}}' youyidian-notification-center
|
|
printf 'FRP_SERVICE='
|
|
systemctl is-active frpc-notify-entry.service
|