mirror of
https://gitcode.com/JianFeeeee/webui4frpc.git
synced 2026-09-20 08:57:55 +00:00
chore: 发布脚本 — 全平台构建打包 + gitcode release 资产上传
- scripts/release.sh: 前端构建 → 6 平台交叉编译 (linux/windows/darwin × amd64/arm64) → tar.gz/zip 打包 → SHA256SUMS;子目录隔离同名二进制 - scripts/upload_assets.py: gitcode 两步上传流程 (upload_url 签名 → urllib PUT 到 OBS),curl 会 401 但 urllib 干净成功 - 首次发布 v0.1.0: 6 个平台安装包 + SHA256SUMS 已上传,下载校验一致
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@ -33,3 +33,6 @@ gui-test-screenshots/
|
|||||||
# vendored deps (regenerable; keep out of git)
|
# vendored deps (regenerable; keep out of git)
|
||||||
vendor/
|
vendor/
|
||||||
.pi-glla/
|
.pi-glla/
|
||||||
|
|
||||||
|
# 发布产物(由 scripts/release.sh 生成)
|
||||||
|
dist/
|
||||||
|
|||||||
137
scripts/release.sh
Executable file
137
scripts/release.sh
Executable file
@ -0,0 +1,137 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# webui4frpc 多平台发布脚本
|
||||||
|
#
|
||||||
|
# 用法:
|
||||||
|
# ./scripts/release.sh [version] # 默认 version=0.1.0
|
||||||
|
#
|
||||||
|
# 行为:
|
||||||
|
# 1. 构建前端 (web/dist) 并同步到 internal/httpapi/dist (go:embed 用)
|
||||||
|
# 2. 交叉编译全平台单二进制 (linux/windows/darwin × amd64/arm64)
|
||||||
|
# 3. 打包各平台安装包:
|
||||||
|
# - linux/darwin: tar.gz (二进制 + README + systemd 模板)
|
||||||
|
# - windows: zip (exe + README)
|
||||||
|
# 4. 生成 SHA256SUMS
|
||||||
|
# 5. 输出到 dist/artifacts/<version>/
|
||||||
|
#
|
||||||
|
# 环境变量:
|
||||||
|
# GOPROXY_OVERRIDE 覆盖 go module 代理 (默认 goproxy.cn,direct, 走本地 7890 代理)
|
||||||
|
# NO_BUILD_FRONTEND 设为 1 跳过前端构建 (用现有 dist)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
VERSION="${1:-0.1.0}"
|
||||||
|
VERSION="${VERSION#v}" # 去掉可能的前导 v
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
OUT="$ROOT/dist/artifacts/$VERSION"
|
||||||
|
GOFLAGS_ENV=""
|
||||||
|
|
||||||
|
# ---- 0. Go module 代理 (内网需走本地代理访问 goproxy.cn) ----
|
||||||
|
if [ -n "${GOPROXY_OVERRIDE:-}" ]; then
|
||||||
|
GOFLAGS_ENV="GOPROXY=${GOPROXY_OVERRIDE}"
|
||||||
|
else
|
||||||
|
GOFLAGS_ENV="GOPROXY=https://goproxy.cn,direct"
|
||||||
|
fi
|
||||||
|
# 若本地 7890 代理存在则导出 (模块下载走代理)
|
||||||
|
if curl -s -o /dev/null -m 2 -x http://127.0.0.1:7890 https://goproxy.cn >/dev/null 2>&1; then
|
||||||
|
export HTTPS_PROXY=http://127.0.0.1:7890 HTTP_PROXY=http://127.0.0.1:7890
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> 版本: $VERSION"
|
||||||
|
mkdir -p "$OUT"
|
||||||
|
|
||||||
|
# ---- 1. 构建前端 ----
|
||||||
|
if [ "${NO_BUILD_FRONTEND:-0}" != "1" ]; then
|
||||||
|
echo "==> 构建前端"
|
||||||
|
( cd "$ROOT/web" && npm run build )
|
||||||
|
rm -rf "$ROOT/internal/httpapi/dist"
|
||||||
|
cp -r "$ROOT/web/dist" "$ROOT/internal/httpapi/dist"
|
||||||
|
else
|
||||||
|
echo "==> 跳过前端构建 (用现有 dist)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- 2. 交叉编译全平台 ----
|
||||||
|
# target 格式: os arch 扩展名 [tar|zip]
|
||||||
|
TARGETS=(
|
||||||
|
"linux amd64" "linux arm64"
|
||||||
|
"windows amd64" "windows arm64"
|
||||||
|
"darwin amd64" "darwin arm64"
|
||||||
|
)
|
||||||
|
|
||||||
|
declare -A PLATFORM_EXT=(
|
||||||
|
[linux-amd64]=tgz [linux-arm64]=tgz
|
||||||
|
[darwin-amd64]=tgz [darwin-arm64]=tgz
|
||||||
|
[windows-amd64]=zip [windows-arm64]=zip
|
||||||
|
)
|
||||||
|
declare -A PLATFORM_BIN=(
|
||||||
|
[linux-amd64]=webui4frpc [linux-arm64]=webui4frpc
|
||||||
|
[darwin-amd64]=webui4frpc [darwin-arm64]=webui4frpc
|
||||||
|
[windows-amd64]=webui4frpc.exe [windows-arm64]=webui4frpc.exe
|
||||||
|
)
|
||||||
|
|
||||||
|
for target in "${TARGETS[@]}"; do
|
||||||
|
os="${target%% *}"; arch="${target##* }"
|
||||||
|
key="$os-$arch"
|
||||||
|
bin="${PLATFORM_BIN[$key]}"
|
||||||
|
# 每个平台编译到独立子目录,避免 darwin/linux 同名二进制互相覆盖
|
||||||
|
bindir="$OUT/bin/$key"
|
||||||
|
mkdir -p "$bindir"
|
||||||
|
echo "==> 编译 $os/$arch"
|
||||||
|
env $GOFLAGS_ENV CGO_ENABLED=0 GOOS="$os" GOARCH="$arch" \
|
||||||
|
go build -trimpath -ldflags="-s -w" \
|
||||||
|
-o "$bindir/$bin" "$ROOT/cmd/webui4frpc"
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---- 3. 打包各平台 ----
|
||||||
|
# 整理 systemd 模板
|
||||||
|
cat > "$OUT/webui4frpc.service.tpl" <<'TPL'
|
||||||
|
[Unit]
|
||||||
|
Description=webui4frpc - visual frpc controller (cluster node)
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/opt/webui4frpc/webui4frpc -addr 0.0.0.0:7500 -user admin -password admin -workdir /opt/webui4frpc/data
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
LimitNOFILE=65535
|
||||||
|
NoNewPrivileges=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
TPL
|
||||||
|
|
||||||
|
for target in "${TARGETS[@]}"; do
|
||||||
|
os="${target%% *}"; arch="${target##* }"
|
||||||
|
key="$os-$arch"
|
||||||
|
bin="${PLATFORM_BIN[$key]}"
|
||||||
|
ext="${PLATFORM_EXT[$key]}"
|
||||||
|
base="webui4frpc-${VERSION}-${os}-${arch}"
|
||||||
|
|
||||||
|
echo "==> 打包 $key -> $base.$ext"
|
||||||
|
cp "$ROOT/README.md" "$OUT/README.md"
|
||||||
|
bindir="$OUT/bin/$key"
|
||||||
|
# staging 目录: 把二进制 + README (+systemd) 放一起再一次性打包
|
||||||
|
stage="$OUT/.stage/$key"
|
||||||
|
rm -rf "$stage"; mkdir -p "$stage"
|
||||||
|
cp "$bindir/$bin" "$stage/$bin"
|
||||||
|
cp "$OUT/README.md" "$stage/README.md"
|
||||||
|
if [ "$ext" = "zip" ]; then
|
||||||
|
( cd "$stage" && zip -q "$OUT/$base.zip" "$bin" README.md )
|
||||||
|
else
|
||||||
|
cp "$OUT/webui4frpc.service.tpl" "$stage/"
|
||||||
|
( cd "$stage" && tar czf "$OUT/$base.tar.gz" "$bin" README.md webui4frpc.service.tpl )
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
rm -rf "$OUT/.stage"
|
||||||
|
|
||||||
|
# ---- 4. 校验和 ----
|
||||||
|
echo "==> 生成 SHA256SUMS"
|
||||||
|
( cd "$OUT" && sha256sum *.tar.gz *.zip 2>/dev/null | tee SHA256SUMS )
|
||||||
|
|
||||||
|
# ---- 5. 汇总 ----
|
||||||
|
echo ""
|
||||||
|
echo "==> 发布产物位于: $OUT"
|
||||||
|
ls -lh "$OUT"
|
||||||
|
echo ""
|
||||||
|
echo "==> 完成"
|
||||||
77
scripts/upload_assets.py
Executable file
77
scripts/upload_assets.py
Executable file
@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""上传 release 资产到 gitcode(两步:取签名 URL → PUT 到 OBS)。
|
||||||
|
|
||||||
|
用法: upload_assets.py <version> <token> [file...]
|
||||||
|
不传 file 时上传该版本目录下所有 tar.gz/zip 与 SHA256SUMS。
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import urllib.request
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
|
REPO = "JianFeeeee/webui4frpc"
|
||||||
|
API = "https://gitcode.com/api/v5/repos"
|
||||||
|
|
||||||
|
|
||||||
|
def get_upload_url(version: str, token: str, filename: str) -> tuple[str, dict]:
|
||||||
|
q = urllib.parse.urlencode({"file_name": filename})
|
||||||
|
url = f"{API}/{REPO}/releases/v{version}/upload_url?{q}"
|
||||||
|
req = urllib.request.Request(url, headers={"private-token": token})
|
||||||
|
with urllib.request.urlopen(req, timeout=20) as r:
|
||||||
|
data = json.loads(r.read())
|
||||||
|
return data["url"], data.get("headers", {})
|
||||||
|
|
||||||
|
|
||||||
|
def put_file(url: str, headers: dict, path: str) -> tuple[int, str]:
|
||||||
|
size = os.path.getsize(path)
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
body = f.read()
|
||||||
|
req = urllib.request.Request(url, data=body, method="PUT")
|
||||||
|
for k, v in headers.items():
|
||||||
|
req.add_header(k, v)
|
||||||
|
req.add_header("Content-Length", str(size))
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=300) as r:
|
||||||
|
return r.status, r.read().decode("utf-8", "replace")[:300]
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
return e.code, e.read().decode("utf-8", "replace")[:300]
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print(__doc__)
|
||||||
|
return 2
|
||||||
|
version, token = sys.argv[1], sys.argv[2]
|
||||||
|
outdir = os.path.join(
|
||||||
|
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
||||||
|
"dist", "artifacts", version,
|
||||||
|
)
|
||||||
|
files = sys.argv[3:] or sorted(
|
||||||
|
f for f in os.listdir(outdir)
|
||||||
|
if f.endswith((".tar.gz", ".zip")) or f == "SHA256SUMS"
|
||||||
|
)
|
||||||
|
failed = 0
|
||||||
|
for name in files:
|
||||||
|
path = os.path.join(outdir, name)
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
print(f"skip (missing): {name}")
|
||||||
|
continue
|
||||||
|
print(f"==> {name} ({os.path.getsize(path)/1048576:.1f} MiB)")
|
||||||
|
try:
|
||||||
|
url, headers = get_upload_url(version, token, name)
|
||||||
|
except Exception as e: # noqa: BLE001
|
||||||
|
print(f" upload_url FAILED: {e}")
|
||||||
|
failed += 1
|
||||||
|
continue
|
||||||
|
status, body = put_file(url, headers, path)
|
||||||
|
ok = 200 <= status < 300
|
||||||
|
print(f" PUT -> {status} {'OK' if ok else body}")
|
||||||
|
if not ok:
|
||||||
|
failed += 1
|
||||||
|
print(f"\n{'ALL OK' if failed == 0 else f'{failed} FAILED'}")
|
||||||
|
return 1 if failed else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user