's'
This commit is contained in:
19
live-platform/config/defaults/business/douyinyoutube.json
Normal file
19
live-platform/config/defaults/business/douyinyoutube.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"id": "douyinyoutube",
|
||||
"name": "无人直播(抖音/TikTok → YouTube / SRS / HDMI)",
|
||||
"enabled": true,
|
||||
"layer": "business",
|
||||
"install_marker": "/opt/live/business/douyinyoutube/.installed",
|
||||
"ffmpeg": {
|
||||
"template_id": "default",
|
||||
"auto_restart": true,
|
||||
"retry_seconds": 15
|
||||
},
|
||||
"streams": {
|
||||
"douyin_input_url": "",
|
||||
"youtube_rtmp_key": "",
|
||||
"srs_publish_url": "",
|
||||
"hdmi_device": ""
|
||||
},
|
||||
"note": "具体推流参数仍可由 PM2 脚本与 config/*.ini 管理;此处为声明式元数据,供控制台与恢复策略使用"
|
||||
}
|
||||
16
live-platform/config/defaults/network.json
Normal file
16
live-platform/config/defaults/network.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": 1,
|
||||
"wifi": {
|
||||
"enabled": true,
|
||||
"connection_name": "live-wifi",
|
||||
"ssid": "live",
|
||||
"psk": "12345678"
|
||||
},
|
||||
"ipv6": {
|
||||
"disable": true
|
||||
},
|
||||
"android_proxy": {
|
||||
"mode": "gateway_optional",
|
||||
"description": "可选透明网关或 ShellCrash 规则;具体由 ShellCrash 与 network 脚本实现"
|
||||
}
|
||||
}
|
||||
12
live-platform/config/defaults/services/android_panel.json
Normal file
12
live-platform/config/defaults/services/android_panel.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"id": "android_panel",
|
||||
"layer": "infra",
|
||||
"enabled": true,
|
||||
"manager": "systemd",
|
||||
"ports": { "http": 5000 },
|
||||
"env": {
|
||||
"ENABLE_ANDROID_PANEL": "1",
|
||||
"ANDROID_PANEL_PORT": "5000",
|
||||
"ANDROID_PANEL_DIR": "/opt/live-modules/web-scrcpy"
|
||||
}
|
||||
}
|
||||
9
live-platform/config/defaults/services/caddy.json
Normal file
9
live-platform/config/defaults/services/caddy.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"id": "caddy",
|
||||
"layer": "infra",
|
||||
"enabled": false,
|
||||
"manager": "systemd",
|
||||
"description": "可选:统一 live.local:80 入口,反代控制台静态 + /api → FastAPI",
|
||||
"ports": { "http": 80, "https": 443 },
|
||||
"template": "/opt/live/templates/Caddyfile"
|
||||
}
|
||||
13
live-platform/config/defaults/services/filebrowser.json
Normal file
13
live-platform/config/defaults/services/filebrowser.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "filebrowser",
|
||||
"layer": "infra",
|
||||
"enabled": true,
|
||||
"manager": "docker",
|
||||
"ports": { "http": 8082 },
|
||||
"env": {
|
||||
"ENABLE_FILEBROWSER": "1",
|
||||
"FILEBROWSER_PORT": "8082",
|
||||
"FILEBROWSER_USER": "live",
|
||||
"FILEBROWSER_PASS": "12345678"
|
||||
}
|
||||
}
|
||||
8
live-platform/config/defaults/services/frp.json
Normal file
8
live-platform/config/defaults/services/frp.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"id": "frp",
|
||||
"layer": "infra",
|
||||
"enabled": false,
|
||||
"manager": "systemd",
|
||||
"description": "占位:内网穿透,由运维填入 frpc 配置后启用",
|
||||
"ports": {}
|
||||
}
|
||||
8
live-platform/config/defaults/services/homepage.json
Normal file
8
live-platform/config/defaults/services/homepage.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"id": "homepage",
|
||||
"layer": "infra",
|
||||
"enabled": true,
|
||||
"manager": "docker",
|
||||
"ports": { "http": 3080 },
|
||||
"env": { "ENABLE_HOMEPAGE": "1", "HOMEPAGE_PORT": "3080" }
|
||||
}
|
||||
13
live-platform/config/defaults/services/neko.json
Normal file
13
live-platform/config/defaults/services/neko.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "neko",
|
||||
"layer": "infra",
|
||||
"enabled": false,
|
||||
"manager": "docker",
|
||||
"ports": { "http": 9200 },
|
||||
"env": {
|
||||
"ENABLE_NEKO": "0",
|
||||
"NEKO_HTTP_PORT": "9200",
|
||||
"NEKO_USER_PASS": "live",
|
||||
"NEKO_ADMIN_PASS": "admin"
|
||||
}
|
||||
}
|
||||
8
live-platform/config/defaults/services/netdata.json
Normal file
8
live-platform/config/defaults/services/netdata.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"id": "netdata",
|
||||
"layer": "infra",
|
||||
"enabled": true,
|
||||
"manager": "native",
|
||||
"ports": { "http": 19999 },
|
||||
"env": { "NETDATA_PORT": "19999", "ENABLE_NETDATA": "1" }
|
||||
}
|
||||
11
live-platform/config/defaults/services/shellcrash.json
Normal file
11
live-platform/config/defaults/services/shellcrash.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"id": "shellcrash",
|
||||
"layer": "infra",
|
||||
"enabled": true,
|
||||
"manager": "systemd",
|
||||
"ports": {},
|
||||
"env": {
|
||||
"SHELLCRASH_DIR": "/etc/ShellCrash",
|
||||
"ENABLE_SHELLCRASH": "1"
|
||||
}
|
||||
}
|
||||
16
live-platform/config/defaults/services/srs.json
Normal file
16
live-platform/config/defaults/services/srs.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "srs",
|
||||
"layer": "infra",
|
||||
"enabled": true,
|
||||
"manager": "docker",
|
||||
"ports": {
|
||||
"http": 8080,
|
||||
"rtmp": 1935,
|
||||
"api": 1985
|
||||
},
|
||||
"env": {
|
||||
"SRS_HTTP_PORT": "8080",
|
||||
"SRS_RTMP_PORT": "1935",
|
||||
"SRS_API_PORT": "1985"
|
||||
}
|
||||
}
|
||||
13
live-platform/config/defaults/services/webtty.json
Normal file
13
live-platform/config/defaults/services/webtty.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "webtty",
|
||||
"layer": "core",
|
||||
"enabled": true,
|
||||
"manager": "systemd",
|
||||
"ports": { "http": 7681 },
|
||||
"env": {
|
||||
"ENABLE_WEBTTY": "1",
|
||||
"WEBTTY_PORT": "7681",
|
||||
"WEBTTY_USER": "live",
|
||||
"WEBTTY_PASS": "12345678"
|
||||
}
|
||||
}
|
||||
8
live-platform/config/defaults/services/wireguard.json
Normal file
8
live-platform/config/defaults/services/wireguard.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"id": "wireguard",
|
||||
"layer": "infra",
|
||||
"enabled": false,
|
||||
"manager": "systemd",
|
||||
"description": "占位:VPN 接口,与 ShellCrash 并存时需规划路由",
|
||||
"ports": {}
|
||||
}
|
||||
21
live-platform/config/defaults/system.json
Normal file
21
live-platform/config/defaults/system.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"version": 1,
|
||||
"platform": {
|
||||
"arch": "auto",
|
||||
"description": "auto = detect via uname -m (aarch64/arm64/x86_64)"
|
||||
},
|
||||
"identity": {
|
||||
"hostname_alias": "live",
|
||||
"mdns_enabled": true
|
||||
},
|
||||
"paths": {
|
||||
"state_dir": "/opt/live/state",
|
||||
"logs_dir": "/opt/live/logs",
|
||||
"generated_dir": "/opt/live/generated"
|
||||
},
|
||||
"web_console": {
|
||||
"api_port": 8001,
|
||||
"public_host": "live.local",
|
||||
"note": "生产环境由 Caddy :80 反代到静态资源与 API"
|
||||
}
|
||||
}
|
||||
13
live-platform/config/templates/Caddyfile.sample
Normal file
13
live-platform/config/templates/Caddyfile.sample
Normal file
@@ -0,0 +1,13 @@
|
||||
# 将本文件复制到 /opt/live/templates/Caddyfile 并按实际路径修改
|
||||
# 目标:http://live.local → 静态控制台 + /api/* → FastAPI
|
||||
|
||||
{$LIVE_DOMAIN:live.local} {
|
||||
encode gzip
|
||||
# 静态(Next export 由 web.py 同端口托管时可改为 reverse_proxy)
|
||||
handle_path /api/* {
|
||||
reverse_proxy 127.0.0.1:8001
|
||||
}
|
||||
handle {
|
||||
reverse_proxy 127.0.0.1:8001
|
||||
}
|
||||
}
|
||||
68
live-platform/scripts/doctor.sh
Normal file
68
live-platform/scripts/doctor.sh
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
# 一键体检:网络 / 端口 / adb / ffmpeg / docker / 服务状态(模块化输出)
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
REPO="$(cd "$ROOT/.." && pwd)"
|
||||
# shellcheck source=/dev/null
|
||||
source "$ROOT/scripts/lib/common.sh"
|
||||
|
||||
echo "=== Live Platform Doctor ==="
|
||||
echo "arch: $(detect_arch)"
|
||||
echo "hostname: $(hostname -f 2>/dev/null || hostname)"
|
||||
|
||||
section() { echo ""; echo "--- $1 ---"; }
|
||||
|
||||
section "loopback / DNS"
|
||||
ping -c1 -W2 127.0.0.1 >/dev/null 2>&1 && echo "ping 127.0.0.1: ok" || echo "ping 127.0.0.1: fail"
|
||||
getent hosts live.local >/dev/null 2>&1 && echo "live.local resolves: yes" || echo "live.local resolves: (optional mdns)"
|
||||
|
||||
section "config"
|
||||
if [[ -f /opt/live/config/system.json ]]; then
|
||||
echo "system.json: present"
|
||||
else
|
||||
echo "system.json: MISSING (run install-core)"
|
||||
fi
|
||||
if [[ -f /opt/live/generated/system-stack.env ]]; then
|
||||
echo "generated env: present"
|
||||
else
|
||||
echo "generated env: missing"
|
||||
fi
|
||||
|
||||
section "ports (LISTEN)"
|
||||
command -v ss >/dev/null 2>&1 && ss -tlnp 2>/dev/null | head -n 40 || netstat -tlnp 2>/dev/null | head -n 40 || true
|
||||
|
||||
section "adb"
|
||||
if command -v adb >/dev/null 2>&1; then
|
||||
adb devices -l || true
|
||||
else
|
||||
echo "adb not installed"
|
||||
fi
|
||||
|
||||
section "ffmpeg"
|
||||
command -v ffmpeg >/dev/null 2>&1 && ffmpeg -version | head -n1 || echo "ffmpeg missing"
|
||||
|
||||
section "docker"
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
docker info >/dev/null 2>&1 && echo "docker: daemon ok" || echo "docker: daemon unreachable"
|
||||
else
|
||||
echo "docker not installed"
|
||||
fi
|
||||
|
||||
section "proxy hint (ShellCrash)"
|
||||
if [[ -d /etc/ShellCrash ]]; then
|
||||
echo "ShellCrash dir: /etc/ShellCrash"
|
||||
else
|
||||
echo "ShellCrash dir: not found"
|
||||
fi
|
||||
|
||||
if [[ -f "$REPO/web.py" ]] && command -v curl >/dev/null 2>&1; then
|
||||
section "API"
|
||||
PORT="${PORT:-8001}"
|
||||
curl -sS -m 3 "http://127.0.0.1:${PORT}/health" && echo "" || echo "health check failed on :$PORT"
|
||||
section "Port 80 (live-edge / Caddy)"
|
||||
systemctl is-active live-edge.service 2>/dev/null || echo "live-edge.service: inactive"
|
||||
curl -sS -m 5 -o /dev/null -w "GET http://127.0.0.1/ → http_code=%{http_code}\n" "http://127.0.0.1/" || echo "curl :80 failed"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Doctor finished ==="
|
||||
42
live-platform/scripts/install-core.sh
Normal file
42
live-platform/scripts/install-core.sh
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
# 幂等:同步配置中心默认 JSON → /opt/live/config,生成 system-stack.env,安装 Hub(无业务模块)
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
REPO="$(cd "$ROOT/.." && pwd)"
|
||||
# shellcheck source=/dev/null
|
||||
source "$ROOT/scripts/lib/common.sh"
|
||||
|
||||
ARCH="$(detect_arch)"
|
||||
echo "[live-platform] install-core arch=$ARCH"
|
||||
|
||||
ensure_live_dirs
|
||||
|
||||
if [[ ! -f /opt/live/config/system.json ]]; then
|
||||
cp -a "$ROOT/config/defaults/system.json" /opt/live/config/system.json
|
||||
fi
|
||||
if [[ ! -f /opt/live/config/network.json ]]; then
|
||||
cp -a "$ROOT/config/defaults/network.json" /opt/live/config/network.json
|
||||
fi
|
||||
if command -v rsync >/dev/null 2>&1; then
|
||||
rsync -a --ignore-existing "$ROOT/config/defaults/services/" /opt/live/config/services/
|
||||
rsync -a --ignore-existing "$ROOT/config/defaults/business/" /opt/live/config/business/
|
||||
else
|
||||
cp -n "$ROOT/config/defaults/services/"*.json /opt/live/config/services/ 2>/dev/null || true
|
||||
cp -n "$ROOT/config/defaults/business/"*.json /opt/live/config/business/ 2>/dev/null || true
|
||||
fi
|
||||
|
||||
export LIVE_CONFIG_ROOT="${LIVE_CONFIG_ROOT:-/opt/live/config}"
|
||||
python3 "$ROOT/tools/render_stack_env.py" /opt/live/generated/system-stack.env
|
||||
|
||||
if [[ -n "${LINK_REPO_CONFIG:-}" ]]; then
|
||||
mkdir -p "$REPO/config"
|
||||
ln -sf /opt/live/generated/system-stack.env "$REPO/config/system-stack.env"
|
||||
fi
|
||||
|
||||
if [[ -x "$REPO/scripts/linux/install_hub.sh" ]]; then
|
||||
bash "$REPO/scripts/linux/install_hub.sh" "$@"
|
||||
else
|
||||
echo "[live-platform] WARN: scripts/linux/install_hub.sh missing, skip hub packages"
|
||||
fi
|
||||
|
||||
echo "[live-platform] install-core done. Env: /opt/live/generated/system-stack.env"
|
||||
16
live-platform/scripts/install-live.sh
Normal file
16
live-platform/scripts/install-live.sh
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
# 幂等:仅无人直播业务模块(与 install-core 解耦)
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
REPO="$(cd "$ROOT/.." && pwd)"
|
||||
|
||||
if [[ -x "$REPO/scripts/linux/install_business.sh" ]]; then
|
||||
bash "$REPO/scripts/linux/install_business.sh" "$@"
|
||||
else
|
||||
echo "[live-platform] ERROR: scripts/linux/install_business.sh not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p /opt/live/business/douyinyoutube
|
||||
touch /opt/live/business/douyinyoutube/.installed
|
||||
echo "[live-platform] install-live done"
|
||||
29
live-platform/scripts/lib/common.sh
Normal file
29
live-platform/scripts/lib/common.sh
Normal file
@@ -0,0 +1,29 @@
|
||||
# shellcheck shell=bash
|
||||
# 被 install-core / install-live / doctor 引用;禁止写业务逻辑,仅路径与权限。
|
||||
|
||||
live_platform_root() {
|
||||
local here
|
||||
here="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
printf '%s\n' "$here"
|
||||
}
|
||||
|
||||
repo_root() {
|
||||
local lp
|
||||
lp="$(live_platform_root)"
|
||||
cd "$lp/.." && pwd
|
||||
}
|
||||
|
||||
detect_arch() {
|
||||
case "$(uname -m)" in
|
||||
aarch64|arm64) echo "arm64" ;;
|
||||
x86_64|amd64) echo "amd64" ;;
|
||||
*) echo "generic" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
ensure_live_dirs() {
|
||||
sudo mkdir -p /opt/live/config/services /opt/live/config/business \
|
||||
/opt/live/generated /opt/live/state /opt/live/logs /opt/live/templates 2>/dev/null || \
|
||||
mkdir -p /opt/live/config/services /opt/live/config/business \
|
||||
/opt/live/generated /opt/live/state /opt/live/logs /opt/live/templates
|
||||
}
|
||||
46
live-platform/tools/render_caddyfile.py
Normal file
46
live-platform/tools/render_caddyfile.py
Normal file
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
"""生成 Caddy 配置:HTTP :80 → 本机 FastAPI+静态(PORT,默认 8001)。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main() -> int:
|
||||
alias = "live"
|
||||
port = "8001"
|
||||
out = Path("/opt/live/generated/Caddyfile")
|
||||
|
||||
cfg = Path("/opt/live/config/system.json")
|
||||
if cfg.is_file():
|
||||
data = json.loads(cfg.read_text(encoding="utf-8"))
|
||||
ident = data.get("identity") or {}
|
||||
web = data.get("web_console") or {}
|
||||
alias = str(ident.get("hostname_alias") or alias)
|
||||
if web.get("api_port") is not None:
|
||||
port = str(web["api_port"])
|
||||
|
||||
if len(sys.argv) >= 2:
|
||||
alias = sys.argv[1]
|
||||
if len(sys.argv) >= 3:
|
||||
port = sys.argv[2]
|
||||
if len(sys.argv) >= 4:
|
||||
out = Path(sys.argv[3])
|
||||
|
||||
host = f"{alias}.local"
|
||||
body = f"""# Generated by live-platform/tools/render_caddyfile.py - do not hand-edit
|
||||
# http://{host}/ and http://<LAN-IP>/ -> 127.0.0.1:{port}
|
||||
|
||||
{host}:80, :80 {{
|
||||
\treverse_proxy 127.0.0.1:{port}
|
||||
}}
|
||||
"""
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
out.write_text(body, encoding="utf-8")
|
||||
print(str(out))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
97
live-platform/tools/render_stack_env.py
Normal file
97
live-platform/tools/render_stack_env.py
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
"""从 /opt/live/config(或 LIVE_CONFIG_ROOT)JSON 配置生成 system-stack.env,供现有 service_ctl / install 脚本读取。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def config_root() -> Path:
|
||||
env = os.environ.get("LIVE_CONFIG_ROOT", "").strip()
|
||||
if env:
|
||||
return Path(env)
|
||||
if Path("/opt/live/config/system.json").is_file():
|
||||
return Path("/opt/live/config")
|
||||
here = Path(__file__).resolve()
|
||||
return here.parent.parent / "config" / "defaults"
|
||||
|
||||
|
||||
def load_json(path: Path) -> dict:
|
||||
if not path.is_file():
|
||||
return {}
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def collect_env(root: Path) -> dict[str, str]:
|
||||
lines: dict[str, str] = {}
|
||||
|
||||
sys_j = load_json(root / "system.json")
|
||||
web = sys_j.get("web_console") or {}
|
||||
ident = sys_j.get("identity") or {}
|
||||
if ident.get("hostname_alias"):
|
||||
lines["HOSTNAME_ALIAS"] = str(ident["hostname_alias"])
|
||||
if web.get("api_port") is not None:
|
||||
lines["PORT"] = str(web["api_port"])
|
||||
|
||||
net = load_json(root / "network.json")
|
||||
wifi = net.get("wifi") or {}
|
||||
if wifi.get("connection_name"):
|
||||
lines["WIFI_CONNECTION_NAME"] = str(wifi["connection_name"])
|
||||
if wifi.get("ssid"):
|
||||
lines["WIFI_SSID"] = str(wifi["ssid"])
|
||||
if wifi.get("psk"):
|
||||
lines["WIFI_PSK"] = str(wifi["psk"])
|
||||
if net.get("ipv6", {}).get("disable"):
|
||||
lines["DISABLE_IPV6"] = "1"
|
||||
|
||||
svc_dir = root / "services"
|
||||
if svc_dir.is_dir():
|
||||
for f in sorted(svc_dir.glob("*.json")):
|
||||
data = load_json(f)
|
||||
env = data.get("env") or {}
|
||||
if not isinstance(env, dict):
|
||||
continue
|
||||
for k, v in env.items():
|
||||
if v is not None and str(v) != "":
|
||||
lines[str(k)] = str(v)
|
||||
|
||||
ports = {}
|
||||
if svc_dir.is_dir():
|
||||
for f in sorted(svc_dir.glob("*.json")):
|
||||
data = load_json(f)
|
||||
pid = data.get("id") or f.stem
|
||||
pr = data.get("ports") or {}
|
||||
if isinstance(pr, dict) and "http" in pr:
|
||||
ports[pid] = pr["http"]
|
||||
# 固定映射:homepage 使用 HOMEPAGE_PORT
|
||||
if "homepage" in ports:
|
||||
lines.setdefault("HOMEPAGE_PORT", str(ports["homepage"]))
|
||||
|
||||
lines.setdefault("ENABLE_MDNS", "1")
|
||||
lines.setdefault("ENABLE_WIFI_PROFILE", "1")
|
||||
lines.setdefault("ENABLE_BROWSER", "1")
|
||||
lines.setdefault("ENABLE_COCKPIT", "1")
|
||||
return lines
|
||||
|
||||
|
||||
def main() -> int:
|
||||
root = config_root()
|
||||
out = Path(os.environ.get("LIVE_ENV_OUT", "/opt/live/generated/system-stack.env"))
|
||||
if len(sys.argv) > 1:
|
||||
out = Path(sys.argv[1])
|
||||
lines = collect_env(root)
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
body = "\n".join(f"{k}={v}" for k, v in sorted(lines.items())) + "\n"
|
||||
out.write_text(
|
||||
"# Generated by live-platform/tools/render_stack_env.py — do not hand-edit\n" + body,
|
||||
encoding="utf-8",
|
||||
)
|
||||
# 开发树:可选同步到仓库 config(由 install 脚本 ln -sf)
|
||||
print(str(out))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user