Compare commits
2 Commits
91d1355721
...
0825170044
| Author | SHA1 | Date | |
|---|---|---|---|
| 0825170044 | |||
| 4fb3fe3fee |
@@ -50,7 +50,10 @@ jobs:
|
|||||||
/mnt/scripts/ci/notify.sh \
|
/mnt/scripts/ci/notify.sh \
|
||||||
/mnt/scripts/ci/lib-ssh.sh \
|
/mnt/scripts/ci/lib-ssh.sh \
|
||||||
/mnt/scripts/ci/compile-site.sh \
|
/mnt/scripts/ci/compile-site.sh \
|
||||||
/mnt/scripts/ci/deploy-site.sh
|
/mnt/scripts/ci/deploy-site.sh \
|
||||||
|
/mnt/scripts/ci/compile-backend.sh \
|
||||||
|
/mnt/scripts/ci/release-server.sh \
|
||||||
|
/mnt/scripts/ci/deploy-server.sh
|
||||||
|
|
||||||
# ── Job 2: OpenAPI Sync Check ────────────────────────────────────────────
|
# ── Job 2: OpenAPI Sync Check ────────────────────────────────────────────
|
||||||
openapi-check:
|
openapi-check:
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
name: Deploy Server
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'server-v[0-9]*.[0-9]*.[0-9]*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: deploy-server
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-server:
|
||||||
|
runs-on: nas
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Compile (Go 控制面, golang:1.25 容器内构建)
|
||||||
|
run: |
|
||||||
|
mkdir -p "$HOME/.cache/pangolin-ci/gomod" "$HOME/.cache/pangolin-ci/gobuild"
|
||||||
|
docker run --rm -v "$PWD:/w" -w /w \
|
||||||
|
-v "$HOME/.cache/pangolin-ci/gomod:/go/pkg/mod" \
|
||||||
|
-v "$HOME/.cache/pangolin-ci/gobuild:/root/.cache/go-build" \
|
||||||
|
golang:1.25 bash scripts/ci/compile-backend.sh
|
||||||
|
|
||||||
|
- name: Test (go test)
|
||||||
|
run: bash scripts/ci/test.sh server
|
||||||
|
|
||||||
|
- name: Release → Forgejo
|
||||||
|
env:
|
||||||
|
FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }}
|
||||||
|
FORGEJO_URL: ${{ secrets.FORGEJO_URL }}
|
||||||
|
TAG: ${{ gitea.ref_name }}
|
||||||
|
run: bash scripts/ci/release-server.sh "$TAG"
|
||||||
|
|
||||||
|
- name: Deploy → pangolin1
|
||||||
|
env:
|
||||||
|
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||||
|
TAG: ${{ gitea.ref_name }}
|
||||||
|
run: bash scripts/ci/deploy-server.sh "$TAG"
|
||||||
@@ -48,6 +48,7 @@ app/kernel/.build/
|
|||||||
# Go 编译产物(mock server 等)
|
# Go 编译产物(mock server 等)
|
||||||
server/mockserver
|
server/mockserver
|
||||||
server/pangolin-server
|
server/pangolin-server
|
||||||
|
server/out/
|
||||||
|
|
||||||
# Flutter / Dart 构建产物与本地配置
|
# Flutter / Dart 构建产物与本地配置
|
||||||
client/android/local.properties
|
client/android/local.properties
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# compile-backend.sh — cross-compile the pangolin Go control-plane binaries
|
||||||
|
# (server / agent / migrate) for the pangolin1 deploy target. CGO disabled:
|
||||||
|
# modernc.org/sqlite is pure Go, no cgo toolchain needed on the runner.
|
||||||
|
# Output: server/out/{pangolin-server,pangolin-agent,pangolin-migrate}.
|
||||||
|
#
|
||||||
|
# Run inside a golang:1.25 container by .gitea/workflows/deploy-server.yml;
|
||||||
|
# this script itself just runs `go build` and assumes it is invoked from the
|
||||||
|
# repo root.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# shellcheck source=scripts/ci/_env.sh
|
||||||
|
. scripts/ci/_env.sh
|
||||||
|
|
||||||
|
cd server
|
||||||
|
mkdir -p out
|
||||||
|
|
||||||
|
echo "==> compile-backend: building pangolin-server"
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o out/pangolin-server ./cmd/server
|
||||||
|
|
||||||
|
echo "==> compile-backend: building pangolin-agent"
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o out/pangolin-agent ./cmd/agent
|
||||||
|
|
||||||
|
echo "==> compile-backend: building pangolin-migrate"
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o out/pangolin-migrate ./cmd/migrate
|
||||||
|
|
||||||
|
echo "==> compile-backend: done — out/ contents:"
|
||||||
|
ls -lh out/
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# deploy-server.sh <tag> — deploy the pangolin control-plane binaries
|
||||||
|
# (pangolin-server / pangolin-agent / pangolin-migrate) to pangolin1, in the
|
||||||
|
# exact manual sequence used for F3/F4: stop → wal checkpoint → backup db →
|
||||||
|
# migrate (rollback db + restart old binary on failure) → swap binaries →
|
||||||
|
# start → healthcheck. Assumes compile-backend.sh has already produced
|
||||||
|
# server/out/{pangolin-server,pangolin-agent,pangolin-migrate}.
|
||||||
|
#
|
||||||
|
# Usage: scripts/ci/deploy-server.sh <tag> (e.g. server-v1.2.3)
|
||||||
|
# Requires env: DEPLOY_SSH_KEY (see lib-ssh.sh).
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# shellcheck source=scripts/ci/lib-ssh.sh
|
||||||
|
. scripts/ci/lib-ssh.sh
|
||||||
|
|
||||||
|
DB=/var/lib/pangolin/pangolin.db
|
||||||
|
BIN=/usr/local/bin
|
||||||
|
TAG="${1:?usage: deploy-server.sh <tag>}"
|
||||||
|
|
||||||
|
# Refuse anything that isn't a strict server-vX.Y.Z[-suffix] tag before it can
|
||||||
|
# reach the remote heredoc / backup paths below (command-injection guard).
|
||||||
|
# An anchored regex is used instead of a `case` glob: a trailing `*` in a
|
||||||
|
# case pattern matches ANY trailing characters (including shell metachars
|
||||||
|
# like `; rm -rf /`), which would defeat the point of this check.
|
||||||
|
if ! [[ "$TAG" =~ ^server-v[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.]+)?$ ]]; then
|
||||||
|
echo "deploy-server: refusing unexpected tag '$TAG'" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# setup_ssh registers the EXIT cleanup trap itself (before writing the key),
|
||||||
|
# so a mid-setup failure still cleans up — see lib-ssh.sh. It exports
|
||||||
|
# SSH_KEY_FILE / DEPLOY_PORT / SSH_KNOWN_HOSTS_FILE / DEPLOY_HOST used below
|
||||||
|
# to build SCP (mirroring the SSH/RSYNC_SSH command-string convention).
|
||||||
|
setup_ssh
|
||||||
|
SCP="scp -i ${SSH_KEY_FILE} -P ${DEPLOY_PORT} -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=${SSH_KNOWN_HOSTS_FILE}"
|
||||||
|
|
||||||
|
echo "==> deploy-server: tag=${TAG} host=${DEPLOY_HOST}"
|
||||||
|
echo "==> deploy-server: uploading binaries to ${DEPLOY_HOST}:/tmp/"
|
||||||
|
$SCP server/out/pangolin-server server/out/pangolin-agent server/out/pangolin-migrate "root@${DEPLOY_HOST}:/tmp/"
|
||||||
|
|
||||||
|
$SSH "root@${DEPLOY_HOST}" "bash -s" <<REMOTE
|
||||||
|
set -euo pipefail
|
||||||
|
systemctl stop pangolin-server
|
||||||
|
runuser -u pangolin -- sqlite3 "$DB" 'PRAGMA wal_checkpoint(TRUNCATE);'
|
||||||
|
cp -p "$DB" "$DB.bak-pre-$TAG"
|
||||||
|
if ! runuser -u pangolin -- env DB_DRIVER=sqlite DB_DSN=$DB /tmp/pangolin-migrate up; then
|
||||||
|
echo "!! migrate 失败,回滚"; cp -p "$DB.bak-pre-$TAG" "$DB"; systemctl start pangolin-server; exit 1
|
||||||
|
fi
|
||||||
|
cp -p "$BIN/pangolin-server" "$BIN/pangolin-server.bak-$TAG" || true
|
||||||
|
install -m755 /tmp/pangolin-server "$BIN/pangolin-server"
|
||||||
|
install -m755 /tmp/pangolin-agent "$BIN/pangolin-agent"
|
||||||
|
install -m755 /tmp/pangolin-migrate "$BIN/pangolin-migrate"
|
||||||
|
systemctl start pangolin-server
|
||||||
|
systemctl is-active pangolin-server
|
||||||
|
REMOTE
|
||||||
|
|
||||||
|
curl -fsS -m 10 --retry 5 --retry-connrefused "http://${DEPLOY_HOST}:8080/healthz" >/dev/null && echo "healthz OK"
|
||||||
|
|
||||||
|
echo "==> deploy-server: done"
|
||||||
@@ -59,18 +59,36 @@ forgejo_release_ensure() {
|
|||||||
rm -f "$get_body_file"
|
rm -f "$get_body_file"
|
||||||
|
|
||||||
echo "==> forgejo: creating release ${tag}"
|
echo "==> forgejo: creating release ${tag}"
|
||||||
local create_code_file create_body_file create_code
|
local create_code_file create_body_file create_code create_req_file
|
||||||
create_code_file="/tmp/forgejo_create_code.$$"
|
create_code_file="/tmp/forgejo_create_code.$$"
|
||||||
create_body_file="/tmp/forgejo_create_body.$$.json"
|
create_body_file="/tmp/forgejo_create_body.$$.json"
|
||||||
|
create_req_file="/tmp/forgejo_create_req.$$.json"
|
||||||
|
|
||||||
|
# Build the JSON request body via python3's json.dumps rather than raw
|
||||||
|
# string interpolation, so a tag/title containing `"` / `\` / control
|
||||||
|
# characters can't break out of the JSON structure (json-injection guard).
|
||||||
|
# Values are piped in NUL-separated on stdin — never interpolated into the
|
||||||
|
# python source — and no $() command substitution is used.
|
||||||
|
printf '%s\0%s\0' "$tag" "$title" | python3 -c '
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
raw = sys.stdin.buffer.read()
|
||||||
|
tag, title = (part.decode() for part in raw.split(b"\0")[:2])
|
||||||
|
json.dump(
|
||||||
|
{"tag_name": tag, "name": title, "draft": False, "prerelease": False},
|
||||||
|
sys.stdout,
|
||||||
|
)
|
||||||
|
' > "$create_req_file"
|
||||||
|
|
||||||
curl "${FORGEJO_CURL_TLS[@]}" -s -o "$create_body_file" -w '%{http_code}' \
|
curl "${FORGEJO_CURL_TLS[@]}" -s -o "$create_body_file" -w '%{http_code}' \
|
||||||
-X POST "${FORGEJO_URL}/api/v1/repos/${FORGEJO_REPO}/releases" \
|
-X POST "${FORGEJO_URL}/api/v1/repos/${FORGEJO_REPO}/releases" \
|
||||||
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "{\"tag_name\":\"${tag}\",\"name\":\"${title}\",\"draft\":false,\"prerelease\":false}" \
|
--data @"$create_req_file" \
|
||||||
> "$create_code_file"
|
> "$create_code_file"
|
||||||
read -r create_code < "$create_code_file"
|
read -r create_code < "$create_code_file"
|
||||||
rm -f "$create_code_file"
|
rm -f "$create_code_file" "$create_req_file"
|
||||||
|
|
||||||
if [ "$create_code" -lt 200 ] || [ "$create_code" -ge 300 ]; then
|
if [ "$create_code" -lt 200 ] || [ "$create_code" -ge 300 ]; then
|
||||||
echo "==> forgejo: release create FAILED (HTTP ${create_code})" >&2
|
echo "==> forgejo: release create FAILED (HTTP ${create_code})" >&2
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# release-server.sh — ensure the Forgejo release for a server-v* tag exists
|
||||||
|
# and upload the three compiled binaries as release assets. Assumes
|
||||||
|
# compile-backend.sh has already produced
|
||||||
|
# server/out/{pangolin-server,pangolin-agent,pangolin-migrate}.
|
||||||
|
#
|
||||||
|
# Usage: scripts/ci/release-server.sh <tag> (e.g. server-v1.2.3)
|
||||||
|
# Requires env: FORGEJO_URL, FORGEJO_TOKEN (see lib-forgejo.sh).
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TAG="${1:?usage: release-server.sh <tag>}"
|
||||||
|
|
||||||
|
# shellcheck source=scripts/ci/_env.sh
|
||||||
|
. scripts/ci/_env.sh
|
||||||
|
# shellcheck source=scripts/ci/lib-forgejo.sh
|
||||||
|
. scripts/ci/lib-forgejo.sh
|
||||||
|
|
||||||
|
# No command substitution ($()): ver_from_tag prints to stdout, captured via
|
||||||
|
# a temp file + `read`, same no-substitution pattern as lib-forgejo.sh.
|
||||||
|
ver_file="/tmp/release_server_ver.$$"
|
||||||
|
ver_from_tag server "$TAG" > "$ver_file"
|
||||||
|
VER=""
|
||||||
|
read -r VER < "$ver_file"
|
||||||
|
rm -f "$ver_file"
|
||||||
|
|
||||||
|
echo "==> release-server: tag=${TAG} ver=${VER}"
|
||||||
|
|
||||||
|
forgejo_release_ensure "$TAG" "server ${VER}"
|
||||||
|
|
||||||
|
forgejo_upload_asset "$TAG" server/out/pangolin-server
|
||||||
|
forgejo_upload_asset "$TAG" server/out/pangolin-agent
|
||||||
|
forgejo_upload_asset "$TAG" server/out/pangolin-migrate
|
||||||
|
|
||||||
|
echo "==> release-server: done"
|
||||||
Reference in New Issue
Block a user