Compare commits
3 Commits
e5d3ff42f5
...
d5e180740e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5e180740e | ||
|
|
3ce51c4cbc | ||
|
|
1023d7a944 |
@ -1,687 +0,0 @@
|
||||
name: Auto Tag
|
||||
|
||||
# Runs on every merge to master — reads the latest semver tag, increments
|
||||
# the patch version, pushes a new tag, then runs release builds in this workflow.
|
||||
# workflow_dispatch allows manual triggering when Gitea drops a push event.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: auto-tag-master
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
autotag:
|
||||
runs-on: linux-amd64
|
||||
container:
|
||||
image: alpine:latest
|
||||
outputs:
|
||||
release_tag: ${{ steps.bump.outputs.release_tag }}
|
||||
steps:
|
||||
- name: Bump patch version and create tag
|
||||
id: bump
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
apk add --no-cache curl jq git
|
||||
|
||||
API="http://172.0.0.29:3000/api/v1/repos/$GITHUB_REPOSITORY"
|
||||
|
||||
# Checkout the source so we can read Cargo.toml
|
||||
git init
|
||||
git remote add origin "http://oauth2:${RELEASE_TOKEN}@172.0.0.29:3000/${GITHUB_REPOSITORY}.git"
|
||||
git fetch --depth=1 origin "$GITHUB_SHA"
|
||||
git checkout FETCH_HEAD
|
||||
git config user.name "gitea-actions[bot]"
|
||||
git config user.email "gitea-actions@local"
|
||||
|
||||
# Read the version declared in Cargo.toml
|
||||
CARGO_VERSION=$(grep '^version' src-tauri/Cargo.toml | head -1 | sed 's/version = "//;s/"//')
|
||||
CARGO_TAG="v${CARGO_VERSION}"
|
||||
echo "Cargo.toml declares: $CARGO_TAG"
|
||||
|
||||
# Get the latest clean semver tag (vX.Y.Z only, ignore rc/test suffixes)
|
||||
LATEST=$(curl -s "$API/tags?limit=50" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | \
|
||||
jq -r '.[].name' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | \
|
||||
sort -V | tail -1)
|
||||
echo "Latest git tag: ${LATEST:-none}"
|
||||
|
||||
# Version resolution:
|
||||
# 1. Cargo.toml > latest tag → use Cargo.toml (major/minor bump)
|
||||
# 2. Cargo.toml == latest tag → tag already exists, use it for builds
|
||||
# 3. Cargo.toml < latest tag → auto-increment patch on latest tag
|
||||
if [ -z "$LATEST" ]; then
|
||||
NEXT="$CARGO_TAG"
|
||||
elif [ "$(printf '%s\n' "$LATEST" "$CARGO_TAG" | sort -V | tail -1)" = "$CARGO_TAG" ]; then
|
||||
# Cargo.toml >= latest tag (covers both "ahead" and "equal" cases)
|
||||
NEXT="$CARGO_TAG"
|
||||
if [ "$CARGO_TAG" = "$LATEST" ]; then
|
||||
echo "Cargo.toml matches latest tag — reusing $NEXT for builds"
|
||||
else
|
||||
echo "Cargo.toml version $CARGO_TAG is ahead of $LATEST — using Cargo.toml"
|
||||
fi
|
||||
else
|
||||
MAJOR=$(echo "$LATEST" | cut -d. -f1 | tr -d 'v')
|
||||
MINOR=$(echo "$LATEST" | cut -d. -f2)
|
||||
PATCH=$(echo "$LATEST" | cut -d. -f3)
|
||||
NEXT="v${MAJOR}.${MINOR}.$((PATCH + 1))"
|
||||
fi
|
||||
|
||||
echo "Latest tag: ${LATEST:-none} → Next: $NEXT"
|
||||
|
||||
if git ls-remote --exit-code --tags origin "refs/tags/$NEXT" >/dev/null 2>&1; then
|
||||
echo "Tag $NEXT already exists; builds will target this tag."
|
||||
else
|
||||
git tag -a "$NEXT" -m "Release $NEXT"
|
||||
git push origin "refs/tags/$NEXT"
|
||||
echo "Tag $NEXT pushed successfully"
|
||||
fi
|
||||
|
||||
# Export for downstream jobs — avoids git-describe guessing wrong tag
|
||||
echo "release_tag=$NEXT" >> "$GITHUB_OUTPUT"
|
||||
|
||||
changelog:
|
||||
needs: autotag
|
||||
runs-on: linux-amd64
|
||||
container:
|
||||
image: alpine:latest
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
set -eu
|
||||
apk add --no-cache git curl jq
|
||||
|
||||
- name: Checkout (full history + all tags)
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
git init
|
||||
git remote add origin \
|
||||
"http://oauth2:${RELEASE_TOKEN}@172.0.0.29:3000/${GITHUB_REPOSITORY}.git"
|
||||
git fetch --unshallow origin || git fetch --depth=2147483647 origin || true
|
||||
git fetch --tags origin
|
||||
git checkout "$GITHUB_SHA" 2>/dev/null || git checkout FETCH_HEAD
|
||||
git config user.name "gitea-actions[bot]"
|
||||
git config user.email "gitea-actions@local"
|
||||
|
||||
- name: Install git-cliff
|
||||
run: |
|
||||
set -eu
|
||||
CLIFF_VER="2.7.0"
|
||||
curl -fsSL \
|
||||
"https://github.com/orhun/git-cliff/releases/download/v${CLIFF_VER}/git-cliff-${CLIFF_VER}-x86_64-unknown-linux-musl.tar.gz" \
|
||||
| tar -xz --strip-components=1 -C /usr/local/bin \
|
||||
"git-cliff-${CLIFF_VER}/git-cliff"
|
||||
|
||||
- name: Generate changelog
|
||||
env:
|
||||
RELEASE_TAG: ${{ needs.autotag.outputs.release_tag }}
|
||||
run: |
|
||||
set -eu
|
||||
CURRENT_TAG="${RELEASE_TAG}"
|
||||
echo "Building changelog for $CURRENT_TAG"
|
||||
|
||||
# Verify the tag is present locally after fetch
|
||||
if ! git rev-parse "refs/tags/${CURRENT_TAG}" >/dev/null 2>&1; then
|
||||
echo "ERROR: tag ${CURRENT_TAG} not found locally after fetch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git-cliff --config cliff.toml --output CHANGELOG.md
|
||||
PREV_TAG=$(git tag --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \
|
||||
| grep -v "^${CURRENT_TAG}$" | head -1 || echo "")
|
||||
if [ -n "$PREV_TAG" ]; then
|
||||
git-cliff --config cliff.toml --tag "$CURRENT_TAG" --strip all > /tmp/release_body.md || true
|
||||
else
|
||||
echo "No previous tag found, generating from git commits"
|
||||
git log --pretty=format:"- %s" > /tmp/release_body.md || true
|
||||
fi
|
||||
echo "=== Release body preview ==="
|
||||
cat /tmp/release_body.md
|
||||
|
||||
- name: Create or update Gitea release
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
RELEASE_TAG: ${{ needs.autotag.outputs.release_tag }}
|
||||
run: |
|
||||
set -eu
|
||||
TAG="${RELEASE_TAG}"
|
||||
API="http://172.0.0.29:3000/api/v1/repos/$GITHUB_REPOSITORY"
|
||||
|
||||
# Try to find an existing release for this tag
|
||||
RELEASE_ID=$(curl -s "$API/releases/tags/$TAG" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | jq -r '.id // empty')
|
||||
|
||||
if [ -z "$RELEASE_ID" ]; then
|
||||
# First run: changelog job owns release creation so build jobs
|
||||
# never race against a missing release object
|
||||
echo "Creating release $TAG..."
|
||||
RELEASE_ID=$(jq -n \
|
||||
--arg tag "$TAG" \
|
||||
--arg name "TFTSR $TAG" \
|
||||
--rawfile body /tmp/release_body.md \
|
||||
'{tag_name: $tag, name: $name, body: $body, draft: false}' \
|
||||
| curl -sf -X POST "$API/releases" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data @- \
|
||||
| jq -r '.id')
|
||||
echo "✓ Release created (id=$RELEASE_ID)"
|
||||
else
|
||||
# Re-run: patch the body only
|
||||
echo "Updating existing release $TAG (id=$RELEASE_ID)..."
|
||||
jq -n --rawfile body /tmp/release_body.md '{body: $body}' \
|
||||
| curl -sf -X PATCH "$API/releases/$RELEASE_ID" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data @-
|
||||
echo "✓ Release body updated"
|
||||
fi
|
||||
|
||||
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then
|
||||
echo "ERROR: Failed to create or locate release for $TAG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Commit CHANGELOG.md to master
|
||||
env:
|
||||
RELEASE_TAG: ${{ needs.autotag.outputs.release_tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG="${RELEASE_TAG}"
|
||||
if ! echo "$TAG" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||
echo "ERROR: Unexpected tag format: $TAG"
|
||||
exit 1
|
||||
fi
|
||||
git add CHANGELOG.md
|
||||
# Only commit if CHANGELOG.md actually changed — avoids ambiguous
|
||||
# exit-code handling from 'git commit || echo' with set -e
|
||||
if git diff --staged --quiet; then
|
||||
echo "No CHANGELOG.md changes to commit"
|
||||
else
|
||||
git commit -m "chore: update CHANGELOG.md for ${TAG} [skip ci]"
|
||||
fi
|
||||
# HEAD:master works in detached HEAD state; 'git push origin master'
|
||||
# would fail because there is no local branch named master
|
||||
git push origin HEAD:master
|
||||
echo "✓ CHANGELOG.md committed to master"
|
||||
|
||||
- name: Upload CHANGELOG.md as release asset
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
RELEASE_TAG: ${{ needs.autotag.outputs.release_tag }}
|
||||
run: |
|
||||
set -eu
|
||||
TAG="${RELEASE_TAG}"
|
||||
API="http://172.0.0.29:3000/api/v1/repos/$GITHUB_REPOSITORY"
|
||||
RELEASE_ID=$(curl -sf "$API/releases/tags/$TAG" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | jq -r '.id')
|
||||
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then
|
||||
echo "ERROR: Could not find release for tag $TAG"
|
||||
exit 1
|
||||
fi
|
||||
# Delete existing asset if present to allow re-upload
|
||||
EXISTING_ID=$(curl -sf "$API/releases/$RELEASE_ID" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
| jq -r '.assets[]? | select(.name == "CHANGELOG.md") | .id')
|
||||
if [ -n "$EXISTING_ID" ]; then
|
||||
curl -sf -X DELETE "$API/releases/$RELEASE_ID/assets/$EXISTING_ID" \
|
||||
-H "Authorization: token $RELEASE_TOKEN"
|
||||
fi
|
||||
curl -sf -X POST "$API/releases/$RELEASE_ID/assets" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-F "attachment=@CHANGELOG.md;filename=CHANGELOG.md"
|
||||
echo "✓ CHANGELOG.md uploaded"
|
||||
|
||||
wiki-sync:
|
||||
runs-on: linux-amd64
|
||||
container:
|
||||
image: alpine:latest
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: apk add --no-cache git
|
||||
|
||||
- name: Checkout main repository
|
||||
run: |
|
||||
git init
|
||||
git remote add origin http://172.0.0.29:3000/sarman/tftsr-devops_investigation.git
|
||||
git fetch --depth=1 origin $GITHUB_SHA
|
||||
git checkout FETCH_HEAD
|
||||
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config --global user.email "actions@gitea.local"
|
||||
git config --global user.name "Gitea Actions"
|
||||
git config --global credential.helper ''
|
||||
|
||||
- name: Clone and sync wiki
|
||||
env:
|
||||
WIKI_TOKEN: ${{ secrets.Wiki }}
|
||||
run: |
|
||||
cd /tmp
|
||||
if [ -n "$WIKI_TOKEN" ]; then
|
||||
WIKI_URL="http://${WIKI_TOKEN}@172.0.0.29:3000/sarman/tftsr-devops_investigation.wiki.git"
|
||||
else
|
||||
WIKI_URL="http://172.0.0.29:3000/sarman/tftsr-devops_investigation.wiki.git"
|
||||
fi
|
||||
|
||||
if ! git clone "$WIKI_URL" wiki 2>/dev/null; then
|
||||
echo "Wiki doesn't exist yet, creating initial structure..."
|
||||
mkdir -p wiki
|
||||
cd wiki
|
||||
git init
|
||||
git checkout -b master
|
||||
echo "# Wiki" > Home.md
|
||||
git add Home.md
|
||||
git commit -m "Initial wiki commit"
|
||||
git remote add origin "$WIKI_URL"
|
||||
fi
|
||||
|
||||
cd /tmp/wiki
|
||||
if [ -d "$GITHUB_WORKSPACE/docs/wiki" ]; then
|
||||
cp -v "$GITHUB_WORKSPACE"/docs/wiki/*.md . 2>/dev/null || echo "No wiki files to copy"
|
||||
fi
|
||||
|
||||
git add -A
|
||||
if ! git diff --staged --quiet; then
|
||||
git commit -m "docs: sync from docs/wiki/ at commit ${GITHUB_SHA:0:8}"
|
||||
echo "Pushing to wiki..."
|
||||
if git push origin master; then
|
||||
echo "✓ Wiki successfully synced"
|
||||
else
|
||||
echo "⚠ Wiki push failed - check token permissions"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "No wiki changes to commit"
|
||||
fi
|
||||
|
||||
build-linux-amd64:
|
||||
needs: autotag
|
||||
runs-on: linux-amd64
|
||||
container:
|
||||
image: 172.0.0.29:3000/sarman/trcaa-linux-amd64:rust1.88-node22
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
git init
|
||||
git remote add origin http://172.0.0.29:3000/sarman/tftsr-devops_investigation.git
|
||||
git fetch --depth=1 origin "$GITHUB_SHA"
|
||||
git checkout FETCH_HEAD
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git/db
|
||||
key: ${{ runner.os }}-cargo-linux-amd64-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-linux-amd64-
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-npm-
|
||||
- name: Build
|
||||
env:
|
||||
APPIMAGE_EXTRACT_AND_RUN: "1"
|
||||
run: |
|
||||
npm ci --legacy-peer-deps
|
||||
CI=true npx tauri build --target x86_64-unknown-linux-gnu
|
||||
- name: Upload artifacts
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
API="http://172.0.0.29:3000/api/v1/repos/$GITHUB_REPOSITORY"
|
||||
TAG=$(curl -s "$API/tags?limit=50" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | \
|
||||
jq -r '.[].name' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | \
|
||||
sort -V | tail -1 || true)
|
||||
if [ -z "$TAG" ]; then
|
||||
echo "ERROR: Could not resolve release tag from repository tags."
|
||||
exit 1
|
||||
fi
|
||||
echo "Creating release for $TAG..."
|
||||
curl -sf -X POST "$API/releases" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\":\"$TAG\",\"name\":\"TFTSR $TAG\",\"body\":\"Release $TAG\",\"draft\":false}" || true
|
||||
RELEASE_ID=$(curl -sf "$API/releases/tags/$TAG" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | jq -r '.id')
|
||||
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then
|
||||
echo "ERROR: Failed to get release ID for $TAG"
|
||||
exit 1
|
||||
fi
|
||||
echo "Release ID: $RELEASE_ID"
|
||||
ARTIFACTS=$(find src-tauri/target/x86_64-unknown-linux-gnu/release/bundle -type f \
|
||||
\( -name "*.deb" -o -name "*.rpm" \))
|
||||
if [ -z "$ARTIFACTS" ]; then
|
||||
echo "ERROR: No Linux amd64 artifacts were found to upload."
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$ARTIFACTS" | while IFS= read -r f; do
|
||||
NAME=$(basename "$f")
|
||||
UPLOAD_NAME="linux-amd64-$NAME"
|
||||
echo "Uploading $UPLOAD_NAME..."
|
||||
EXISTING_IDS=$(curl -sf "$API/releases/$RELEASE_ID" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
| jq -r --arg name "$UPLOAD_NAME" '.assets[]? | select(.name == $name) | .id')
|
||||
if [ -n "$EXISTING_IDS" ]; then
|
||||
printf '%s\n' "$EXISTING_IDS" | while IFS= read -r id; do
|
||||
[ -n "$id" ] || continue
|
||||
echo "Deleting existing asset id=$id name=$UPLOAD_NAME before upload..."
|
||||
curl -sf -X DELETE "$API/releases/$RELEASE_ID/assets/$id" \
|
||||
-H "Authorization: token $RELEASE_TOKEN"
|
||||
done
|
||||
fi
|
||||
RESP_FILE=$(mktemp)
|
||||
HTTP_CODE=$(curl -sS -o "$RESP_FILE" -w "%{http_code}" -X POST "$API/releases/$RELEASE_ID/assets" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-F "attachment=@$f;filename=$UPLOAD_NAME")
|
||||
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||
echo "✓ Uploaded $UPLOAD_NAME"
|
||||
else
|
||||
echo "✗ Upload failed for $UPLOAD_NAME (HTTP $HTTP_CODE)"
|
||||
python -c 'import pathlib,sys;print(pathlib.Path(sys.argv[1]).read_text(errors="replace")[:2000])' "$RESP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
build-windows-amd64:
|
||||
needs: autotag
|
||||
runs-on: linux-amd64
|
||||
container:
|
||||
image: 172.0.0.29:3000/sarman/trcaa-windows-cross:rust1.88-node22
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
git init
|
||||
git remote add origin http://172.0.0.29:3000/sarman/tftsr-devops_investigation.git
|
||||
git fetch --depth=1 origin "$GITHUB_SHA"
|
||||
git checkout FETCH_HEAD
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git/db
|
||||
key: ${{ runner.os }}-cargo-windows-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-windows-
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-npm-
|
||||
- name: Build
|
||||
env:
|
||||
CC_x86_64_pc_windows_gnu: x86_64-w64-mingw32-gcc
|
||||
CXX_x86_64_pc_windows_gnu: x86_64-w64-mingw32-g++
|
||||
AR_x86_64_pc_windows_gnu: x86_64-w64-mingw32-ar
|
||||
CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER: x86_64-w64-mingw32-gcc
|
||||
OPENSSL_NO_VENDOR: "0"
|
||||
OPENSSL_STATIC: "1"
|
||||
run: |
|
||||
npm ci --legacy-peer-deps
|
||||
CI=true npx tauri build --target x86_64-pc-windows-gnu
|
||||
- name: Upload artifacts
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
API="http://172.0.0.29:3000/api/v1/repos/$GITHUB_REPOSITORY"
|
||||
TAG=$(curl -s "$API/tags?limit=50" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | \
|
||||
jq -r '.[].name' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | \
|
||||
sort -V | tail -1 || true)
|
||||
if [ -z "$TAG" ]; then
|
||||
echo "ERROR: Could not resolve release tag from repository tags."
|
||||
exit 1
|
||||
fi
|
||||
echo "Creating release for $TAG..."
|
||||
curl -sf -X POST "$API/releases" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\":\"$TAG\",\"name\":\"TFTSR $TAG\",\"body\":\"Release $TAG\",\"draft\":false}" || true
|
||||
RELEASE_ID=$(curl -sf "$API/releases/tags/$TAG" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | jq -r '.id')
|
||||
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then
|
||||
echo "ERROR: Failed to get release ID for $TAG"
|
||||
exit 1
|
||||
fi
|
||||
echo "Release ID: $RELEASE_ID"
|
||||
ARTIFACTS=$(find src-tauri/target/x86_64-pc-windows-gnu/release/bundle -type f \
|
||||
\( -name "*.exe" -o -name "*.msi" \) 2>/dev/null)
|
||||
if [ -z "$ARTIFACTS" ]; then
|
||||
echo "ERROR: No Windows amd64 artifacts were found to upload."
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$ARTIFACTS" | while IFS= read -r f; do
|
||||
NAME=$(basename "$f")
|
||||
echo "Uploading $NAME..."
|
||||
EXISTING_IDS=$(curl -sf "$API/releases/$RELEASE_ID" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
| jq -r --arg name "$NAME" '.assets[]? | select(.name == $name) | .id')
|
||||
if [ -n "$EXISTING_IDS" ]; then
|
||||
printf '%s\n' "$EXISTING_IDS" | while IFS= read -r id; do
|
||||
[ -n "$id" ] || continue
|
||||
echo "Deleting existing asset id=$id name=$NAME before upload..."
|
||||
curl -sf -X DELETE "$API/releases/$RELEASE_ID/assets/$id" \
|
||||
-H "Authorization: token $RELEASE_TOKEN"
|
||||
done
|
||||
fi
|
||||
RESP_FILE=$(mktemp)
|
||||
HTTP_CODE=$(curl -sS -o "$RESP_FILE" -w "%{http_code}" -X POST "$API/releases/$RELEASE_ID/assets" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-F "attachment=@$f;filename=$NAME")
|
||||
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||
echo "✓ Uploaded $NAME"
|
||||
else
|
||||
echo "✗ Upload failed for $NAME (HTTP $HTTP_CODE)"
|
||||
python -c 'import pathlib,sys;print(pathlib.Path(sys.argv[1]).read_text(errors="replace")[:2000])' "$RESP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
build-macos-arm64:
|
||||
needs: autotag
|
||||
runs-on: macos-arm64
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
git init
|
||||
git remote add origin http://172.0.0.29:3000/sarman/tftsr-devops_investigation.git
|
||||
git fetch --depth=1 origin "$GITHUB_SHA"
|
||||
git checkout FETCH_HEAD
|
||||
- name: Build
|
||||
env:
|
||||
MACOSX_DEPLOYMENT_TARGET: "11.0"
|
||||
run: |
|
||||
npm ci --legacy-peer-deps
|
||||
rustup target add aarch64-apple-darwin
|
||||
CI=true npx tauri build --target aarch64-apple-darwin --bundles app
|
||||
APP=$(find src-tauri/target/aarch64-apple-darwin/release/bundle/macos -maxdepth 1 -type d -name "*.app" | head -n 1)
|
||||
if [ -z "$APP" ]; then
|
||||
echo "ERROR: Could not find macOS app bundle"
|
||||
exit 1
|
||||
fi
|
||||
APP_NAME=$(basename "$APP" .app)
|
||||
codesign --deep --force --sign - "$APP"
|
||||
mkdir -p src-tauri/target/aarch64-apple-darwin/release/bundle/dmg
|
||||
DMG=src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/${APP_NAME}.dmg
|
||||
hdiutil create -volname "$APP_NAME" -srcfolder "$APP" -ov -format UDZO "$DMG"
|
||||
- name: Upload artifacts
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
API="http://172.0.0.29:3000/api/v1/repos/$GITHUB_REPOSITORY"
|
||||
TAG=$(curl -s "$API/tags?limit=50" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | \
|
||||
jq -r '.[].name' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | \
|
||||
sort -V | tail -1 || true)
|
||||
if [ -z "$TAG" ]; then
|
||||
echo "ERROR: Could not resolve release tag from repository tags."
|
||||
exit 1
|
||||
fi
|
||||
echo "Creating release for $TAG..."
|
||||
curl -sf -X POST "$API/releases" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\":\"$TAG\",\"name\":\"TFTSR $TAG\",\"body\":\"Release $TAG\",\"draft\":false}" || true
|
||||
RELEASE_ID=$(curl -sf "$API/releases/tags/$TAG" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | jq -r '.id')
|
||||
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then
|
||||
echo "ERROR: Failed to get release ID for $TAG"
|
||||
exit 1
|
||||
fi
|
||||
echo "Release ID: $RELEASE_ID"
|
||||
ARTIFACTS=$(find src-tauri/target/aarch64-apple-darwin/release/bundle -type f -name "*.dmg")
|
||||
if [ -z "$ARTIFACTS" ]; then
|
||||
echo "ERROR: No macOS arm64 DMG artifacts were found to upload."
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$ARTIFACTS" | while IFS= read -r f; do
|
||||
NAME=$(basename "$f")
|
||||
echo "Uploading $NAME..."
|
||||
EXISTING_IDS=$(curl -sf "$API/releases/$RELEASE_ID" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
| jq -r --arg name "$NAME" '.assets[]? | select(.name == $name) | .id')
|
||||
if [ -n "$EXISTING_IDS" ]; then
|
||||
printf '%s\n' "$EXISTING_IDS" | while IFS= read -r id; do
|
||||
[ -n "$id" ] || continue
|
||||
echo "Deleting existing asset id=$id name=$NAME before upload..."
|
||||
curl -sf -X DELETE "$API/releases/$RELEASE_ID/assets/$id" \
|
||||
-H "Authorization: token $RELEASE_TOKEN"
|
||||
done
|
||||
fi
|
||||
RESP_FILE=$(mktemp)
|
||||
HTTP_CODE=$(curl -sS -o "$RESP_FILE" -w "%{http_code}" -X POST "$API/releases/$RELEASE_ID/assets" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-F "attachment=@$f;filename=$NAME")
|
||||
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||
echo "✓ Uploaded $NAME"
|
||||
else
|
||||
echo "✗ Upload failed for $NAME (HTTP $HTTP_CODE)"
|
||||
python -c 'import pathlib,sys;print(pathlib.Path(sys.argv[1]).read_text(errors="replace")[:2000])' "$RESP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
build-linux-arm64:
|
||||
needs: autotag
|
||||
runs-on: linux-amd64
|
||||
container:
|
||||
image: 172.0.0.29:3000/sarman/trcaa-linux-arm64:rust1.88-node22
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
git init
|
||||
git remote add origin http://172.0.0.29:3000/sarman/tftsr-devops_investigation.git
|
||||
git fetch --depth=1 origin "$GITHUB_SHA"
|
||||
git checkout FETCH_HEAD
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git/db
|
||||
key: ${{ runner.os }}-cargo-arm64-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-arm64-
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-npm-
|
||||
- name: Build
|
||||
env:
|
||||
CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc
|
||||
CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++
|
||||
AR_aarch64_unknown_linux_gnu: aarch64-linux-gnu-ar
|
||||
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
|
||||
PKG_CONFIG_SYSROOT_DIR: /usr/aarch64-linux-gnu
|
||||
PKG_CONFIG_PATH: /usr/lib/aarch64-linux-gnu/pkgconfig
|
||||
PKG_CONFIG_ALLOW_CROSS: "1"
|
||||
OPENSSL_NO_VENDOR: "0"
|
||||
OPENSSL_STATIC: "1"
|
||||
APPIMAGE_EXTRACT_AND_RUN: "1"
|
||||
run: |
|
||||
npm ci --legacy-peer-deps
|
||||
CI=true npx tauri build --target aarch64-unknown-linux-gnu --bundles deb,rpm
|
||||
- name: Upload artifacts
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
API="http://172.0.0.29:3000/api/v1/repos/$GITHUB_REPOSITORY"
|
||||
TAG=$(curl -s "$API/tags?limit=50" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | \
|
||||
jq -r '.[].name' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | \
|
||||
sort -V | tail -1 || true)
|
||||
if [ -z "$TAG" ]; then
|
||||
echo "ERROR: Could not resolve release tag from repository tags."
|
||||
exit 1
|
||||
fi
|
||||
echo "Creating release for $TAG..."
|
||||
curl -sf -X POST "$API/releases" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\":\"$TAG\",\"name\":\"TFTSR $TAG\",\"body\":\"Release $TAG\",\"draft\":false}" || true
|
||||
RELEASE_ID=$(curl -sf "$API/releases/tags/$TAG" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | jq -r '.id')
|
||||
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then
|
||||
echo "ERROR: Failed to get release ID for $TAG"
|
||||
exit 1
|
||||
fi
|
||||
echo "Release ID: $RELEASE_ID"
|
||||
ARTIFACTS=$(find src-tauri/target/aarch64-unknown-linux-gnu/release/bundle -type f \
|
||||
\( -name "*.deb" -o -name "*.rpm" -o -name "*.AppImage" \))
|
||||
if [ -z "$ARTIFACTS" ]; then
|
||||
echo "ERROR: No Linux arm64 artifacts were found to upload."
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$ARTIFACTS" | while IFS= read -r f; do
|
||||
NAME=$(basename "$f")
|
||||
UPLOAD_NAME="linux-arm64-$NAME"
|
||||
echo "Uploading $UPLOAD_NAME..."
|
||||
EXISTING_IDS=$(curl -sf "$API/releases/$RELEASE_ID" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
| jq -r --arg name "$UPLOAD_NAME" '.assets[]? | select(.name == $name) | .id')
|
||||
if [ -n "$EXISTING_IDS" ]; then
|
||||
printf '%s\n' "$EXISTING_IDS" | while IFS= read -r id; do
|
||||
[ -n "$id" ] || continue
|
||||
echo "Deleting existing asset id=$id name=$UPLOAD_NAME before upload..."
|
||||
curl -sf -X DELETE "$API/releases/$RELEASE_ID/assets/$id" \
|
||||
-H "Authorization: token $RELEASE_TOKEN"
|
||||
done
|
||||
fi
|
||||
RESP_FILE=$(mktemp)
|
||||
HTTP_CODE=$(curl -sS -o "$RESP_FILE" -w "%{http_code}" -X POST "$API/releases/$RELEASE_ID/assets" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-F "attachment=@$f;filename=$UPLOAD_NAME")
|
||||
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||
echo "✓ Uploaded $UPLOAD_NAME"
|
||||
else
|
||||
echo "✗ Upload failed for $UPLOAD_NAME (HTTP $HTTP_CODE)"
|
||||
python -c 'import pathlib,sys;print(pathlib.Path(sys.argv[1]).read_text(errors="replace")[:2000])' "$RESP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
@ -1,104 +0,0 @@
|
||||
name: Build CI Docker Images
|
||||
|
||||
# Rebuilds the pre-baked builder images and pushes them to the local Gitea
|
||||
# container registry (172.0.0.29:3000).
|
||||
#
|
||||
# WHEN TO RUN:
|
||||
# - Automatically: whenever a Dockerfile under .docker/ changes on master.
|
||||
# - Manually: via workflow_dispatch (e.g. first-time setup, forced rebuild).
|
||||
#
|
||||
# ONE-TIME SERVER PREREQUISITE (run once on 172.0.0.29 before first use):
|
||||
# echo '{"insecure-registries":["172.0.0.29:3000"]}' \
|
||||
# | sudo tee /etc/docker/daemon.json
|
||||
# sudo systemctl restart docker
|
||||
#
|
||||
# Images produced:
|
||||
# 172.0.0.29:3000/sarman/trcaa-linux-amd64:rust1.88-node22
|
||||
# 172.0.0.29:3000/sarman/trcaa-windows-cross:rust1.88-node22
|
||||
# 172.0.0.29:3000/sarman/trcaa-linux-arm64:rust1.88-node22
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '.docker/**'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: build-ci-images
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
REGISTRY: 172.0.0.29:3000
|
||||
REGISTRY_USER: sarman
|
||||
|
||||
jobs:
|
||||
linux-amd64:
|
||||
runs-on: linux-amd64
|
||||
container:
|
||||
image: alpine:latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
apk add --no-cache git docker-cli
|
||||
git init
|
||||
git remote add origin http://172.0.0.29:3000/sarman/tftsr-devops_investigation.git
|
||||
git fetch --depth=1 origin "$GITHUB_SHA"
|
||||
git checkout FETCH_HEAD
|
||||
- name: Build and push linux-amd64 builder
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
echo "$RELEASE_TOKEN" | docker login $REGISTRY -u $REGISTRY_USER --password-stdin
|
||||
docker build \
|
||||
-t $REGISTRY/$REGISTRY_USER/trcaa-linux-amd64:rust1.88-node22 \
|
||||
-f .docker/Dockerfile.linux-amd64 .
|
||||
docker push $REGISTRY/$REGISTRY_USER/trcaa-linux-amd64:rust1.88-node22
|
||||
echo "✓ Pushed $REGISTRY/$REGISTRY_USER/trcaa-linux-amd64:rust1.88-node22"
|
||||
|
||||
windows-cross:
|
||||
runs-on: linux-amd64
|
||||
container:
|
||||
image: alpine:latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
apk add --no-cache git docker-cli
|
||||
git init
|
||||
git remote add origin http://172.0.0.29:3000/sarman/tftsr-devops_investigation.git
|
||||
git fetch --depth=1 origin "$GITHUB_SHA"
|
||||
git checkout FETCH_HEAD
|
||||
- name: Build and push windows-cross builder
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
echo "$RELEASE_TOKEN" | docker login $REGISTRY -u $REGISTRY_USER --password-stdin
|
||||
docker build \
|
||||
-t $REGISTRY/$REGISTRY_USER/trcaa-windows-cross:rust1.88-node22 \
|
||||
-f .docker/Dockerfile.windows-cross .
|
||||
docker push $REGISTRY/$REGISTRY_USER/trcaa-windows-cross:rust1.88-node22
|
||||
echo "✓ Pushed $REGISTRY/$REGISTRY_USER/trcaa-windows-cross:rust1.88-node22"
|
||||
|
||||
linux-arm64:
|
||||
runs-on: linux-amd64
|
||||
container:
|
||||
image: alpine:latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
apk add --no-cache git docker-cli
|
||||
git init
|
||||
git remote add origin http://172.0.0.29:3000/sarman/tftsr-devops_investigation.git
|
||||
git fetch --depth=1 origin "$GITHUB_SHA"
|
||||
git checkout FETCH_HEAD
|
||||
- name: Build and push linux-arm64 builder
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
echo "$RELEASE_TOKEN" | docker login $REGISTRY -u $REGISTRY_USER --password-stdin
|
||||
docker build \
|
||||
-t $REGISTRY/$REGISTRY_USER/trcaa-linux-arm64:rust1.88-node22 \
|
||||
-f .docker/Dockerfile.linux-arm64 .
|
||||
docker push $REGISTRY/$REGISTRY_USER/trcaa-linux-arm64:rust1.88-node22
|
||||
echo "✓ Pushed $REGISTRY/$REGISTRY_USER/trcaa-linux-arm64:rust1.88-node22"
|
||||
@ -1,333 +0,0 @@
|
||||
name: PR Review Automation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, edited]
|
||||
|
||||
|
||||
jobs:
|
||||
review:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
container:
|
||||
image: ubuntu:22.04
|
||||
options: --dns 8.8.8.8 --dns 1.1.1.1
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
apt-get update -qq && apt-get install -y -qq git curl jq python3
|
||||
|
||||
- name: Checkout code
|
||||
shell: bash
|
||||
env:
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git init
|
||||
git remote add origin "https://gogs.tftsr.com/${REPOSITORY}.git"
|
||||
git fetch --depth=1 origin ${{ github.head_ref }}
|
||||
git checkout FETCH_HEAD
|
||||
|
||||
- name: Build review context
|
||||
id: context
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch origin ${{ github.base_ref }}
|
||||
|
||||
# List changed source files (exclude generated/lock files)
|
||||
git diff --name-only origin/${{ github.base_ref }}..HEAD \
|
||||
-- ':!Cargo.lock' ':!package-lock.json' ':!*.lock' \
|
||||
> /tmp/pr_files.txt
|
||||
|
||||
FILE_COUNT=$(wc -l < /tmp/pr_files.txt | tr -d ' ')
|
||||
echo "files_changed=${FILE_COUNT}" >> $GITHUB_OUTPUT
|
||||
|
||||
if [ "$FILE_COUNT" -eq 0 ]; then
|
||||
echo "No reviewable files changed."
|
||||
echo "diff_size=0" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Build context: full file content for each changed file.
|
||||
# Files <= 500 lines: include complete content.
|
||||
# Files > 500 lines: include the per-file diff with generous context (±50 lines).
|
||||
#
|
||||
# Secret scrubbing: match actual credential VALUES only — known API key formats,
|
||||
# or keyword="long_quoted_literal" (25+ chars). Never scrub on keyword alone,
|
||||
# which would silently delete function signatures, variable declarations, and tests.
|
||||
SECRET_PATTERN='AKIA[A-Z0-9]{16}|gh[opsu]_[A-Za-z0-9_]{36,}|xox[baprs]-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24}|(password|token|api_key|secret)[[:space:]]*=[[:space:]]*["'"'"'][A-Za-z0-9+/_!@#-]{25,}["'"'"']'
|
||||
# Only strip lines that are ENTIRELY a long base64 blob (e.g. PEM cert bodies)
|
||||
B64_PATTERN='^[[:space:]]*[A-Za-z0-9+/]{60,}={0,2}[[:space:]]*$'
|
||||
|
||||
> /tmp/pr_context.txt
|
||||
while IFS= read -r file; do
|
||||
[ -f "$file" ] || continue
|
||||
lines=$(wc -l < "$file" | tr -d ' ')
|
||||
printf '\n════════ FILE: %s (%s lines) ════════\n' "$file" "$lines" >> /tmp/pr_context.txt
|
||||
if [ "$lines" -le 500 ]; then
|
||||
# Full file — model sees the complete implementation
|
||||
grep -v -E "$SECRET_PATTERN" "$file" \
|
||||
| grep -v -E "$B64_PATTERN" \
|
||||
>> /tmp/pr_context.txt || true
|
||||
else
|
||||
# Large file — emit annotated diff hunks (±50 lines of context each)
|
||||
printf '[File too large for full view (%s lines) — showing changed sections only]\n' "$lines" >> /tmp/pr_context.txt
|
||||
git diff -U50 origin/${{ github.base_ref }}..HEAD -- "$file" \
|
||||
| grep -v -E "$SECRET_PATTERN" \
|
||||
| grep -v -E "$B64_PATTERN" \
|
||||
>> /tmp/pr_context.txt || true
|
||||
fi
|
||||
done < /tmp/pr_files.txt
|
||||
|
||||
TOTAL=$(wc -l < /tmp/pr_context.txt | tr -d ' ')
|
||||
echo "diff_size=${TOTAL}" >> $GITHUB_OUTPUT
|
||||
|
||||
# Cap at 6000 lines so we stay within the model's context window
|
||||
if [ "$TOTAL" -gt 6000 ]; then
|
||||
head -n 6000 /tmp/pr_context.txt > /tmp/pr_context_capped.txt
|
||||
mv /tmp/pr_context_capped.txt /tmp/pr_context.txt
|
||||
echo "[CONTEXT TRUNCATED at 6000 lines — ${TOTAL} total]" >> /tmp/pr_context.txt
|
||||
fi
|
||||
|
||||
- name: Build codebase index
|
||||
id: index
|
||||
if: steps.context.outputs.diff_size != '0'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Build a compact index of everything that EXISTS in this codebase.
|
||||
# Included in the prompt so the model cannot invent functions/commands/tables
|
||||
# that are not present — any finding referencing something absent from this
|
||||
# index is immediately suspect.
|
||||
{
|
||||
echo "## CODEBASE INDEX"
|
||||
echo "These are the ONLY Tauri commands, TypeScript exports, Rust public functions,"
|
||||
echo "and database tables that exist in this project. Before raising any finding,"
|
||||
echo "confirm that every symbol you cite appears in this list or in the file"
|
||||
echo "contents below. If it does not appear in either, your finding is fabricated."
|
||||
echo ""
|
||||
|
||||
echo "### Registered Tauri commands (lib.rs generate_handler![]):"
|
||||
grep -oE 'commands::[a-z_]+::[a-z_]+' src-tauri/src/lib.rs 2>/dev/null \
|
||||
| sort -u | sed 's/^/ /' || true
|
||||
echo ""
|
||||
|
||||
echo "### TypeScript invoke wrappers (src/lib/tauriCommands.ts):"
|
||||
grep -E '^export (const|interface|type) ' src/lib/tauriCommands.ts 2>/dev/null \
|
||||
| sed 's/^/ /' || true
|
||||
echo ""
|
||||
|
||||
echo "### Public Rust functions in src-tauri/src/commands/:"
|
||||
grep -rh --include='*.rs' '^pub ' src-tauri/src/commands/ 2>/dev/null \
|
||||
| grep 'fn ' | sed 's/^/ /' | sort || true
|
||||
echo ""
|
||||
|
||||
echo "### Database tables (src-tauri/src/db/migrations.rs):"
|
||||
grep -oE '"[0-9]+_[a-z_]+"' src-tauri/src/db/migrations.rs 2>/dev/null \
|
||||
| tr -d '"' | sed 's/^/ /' || true
|
||||
echo ""
|
||||
} > /tmp/codebase_index.txt
|
||||
|
||||
INDEX_LINES=$(wc -l < /tmp/codebase_index.txt | tr -d ' ')
|
||||
echo "index_lines=${INDEX_LINES}" >> $GITHUB_OUTPUT
|
||||
echo "Built codebase index: ${INDEX_LINES} lines"
|
||||
|
||||
- name: Analyze with LLM
|
||||
id: analyze
|
||||
if: steps.context.outputs.diff_size != '0'
|
||||
shell: bash
|
||||
env:
|
||||
LITELLM_URL: http://172.0.0.29:11434/v1
|
||||
LITELLM_API_KEY: ${{ secrets.OLLAMA_API_KEY }}
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
CHANGED_FILES=$(tr '\n' ' ' < /tmp/pr_files.txt)
|
||||
|
||||
# Build prompt file. Use 'printf "%s\n" text' throughout so the format
|
||||
# string is always "%s\n" and content with leading hyphens or embedded
|
||||
# double-dashes is never misinterpreted as a printf option flag.
|
||||
{
|
||||
printf '%s\n\n' 'You are a senior engineer performing a code review.'
|
||||
printf 'PR Title: %s\n' "$PR_TITLE"
|
||||
printf 'Files changed: %s\n\n' "$CHANGED_FILES"
|
||||
printf '%s\n' '---'
|
||||
cat /tmp/codebase_index.txt
|
||||
printf '%s\n\n' '---'
|
||||
printf '%s\n\n' '## Changed file contents'
|
||||
printf '%s\n' 'Each section is the COMPLETE, FINAL file after PR changes (not a diff).'
|
||||
printf '%s\n\n' 'Files over 500 lines show only changed sections with surrounding context.'
|
||||
printf '%s\n' '---'
|
||||
cat /tmp/pr_context.txt
|
||||
printf '%s\n\n' '---'
|
||||
printf '%s\n\n' '## Instructions'
|
||||
printf '%s\n' 'Before raising any finding:'
|
||||
printf '%s\n' '1. Confirm every symbol you cite exists in the CODEBASE INDEX or file'
|
||||
printf '%s\n' ' contents above. If absent from both, discard the finding.'
|
||||
printf '%s\n' '2. Quote the exact line(s) from the file contents that support it.'
|
||||
printf '%s\n' '3. Confirm the issue is genuine, not intentional design.'
|
||||
printf '%s\n\n' '4. If any step fails, discard silently - do not mention it.'
|
||||
printf '%s\n\n' 'Do NOT show reasoning. Only output confirmed issues.'
|
||||
printf '%s\n' 'Severity:'
|
||||
printf '%s\n' '- BLOCKER: fails to compile, corrupts data, or security vulnerability'
|
||||
printf '%s\n' '- WARNING: real risk to address before merge'
|
||||
printf '%s\n\n' '- SUGGESTION: minor improvement, follow-up PR fine'
|
||||
printf '%s\n\n' 'Focus: security bugs, logic errors, data loss, injection, unhandled errors.'
|
||||
printf '%s\n\n' 'Ignore: style, missing comments, speculative future concerns.'
|
||||
printf '%s\n\n' '## Output format (strict)'
|
||||
printf '%s\n\n' '**Summary** (2-3 sentences)'
|
||||
printf '%s\n' '**Findings**'
|
||||
printf '%s\n' '- [SEVERITY] file:line - description'
|
||||
printf '%s\n' ' Evidence: quoted line'
|
||||
printf '%s\n\n' ' Fix: concrete change'
|
||||
printf '%s\n\n' '(Write "No findings." if none.)'
|
||||
printf '%s\n' '**Verdict**: APPROVE / APPROVE WITH COMMENTS / REQUEST CHANGES'
|
||||
} > /tmp/prompt.txt
|
||||
|
||||
# Write body to file — passing 100KB+ JSON as a shell arg hits ARG_MAX.
|
||||
jq -cn \
|
||||
--arg model "qwen3-coder-next" \
|
||||
--rawfile content /tmp/prompt.txt \
|
||||
'{model: $model, messages: [{role: "user", content: $content}], stream: false}' \
|
||||
> /tmp/body.json
|
||||
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] PR #${PR_NUMBER} - Calling liteLLM API ($(wc -c < /tmp/body.json) bytes)..."
|
||||
HTTP_CODE=$(curl -s --max-time 300 --connect-timeout 30 \
|
||||
--retry 3 --retry-delay 10 --retry-connrefused --retry-max-time 300 \
|
||||
-o /tmp/llm_response.json -w "%{http_code}" \
|
||||
-X POST "$LITELLM_URL/chat/completions" \
|
||||
-H "Authorization: Bearer $LITELLM_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data @/tmp/body.json)
|
||||
echo "HTTP status: $HTTP_CODE"
|
||||
echo "Response file size: $(wc -c < /tmp/llm_response.json) bytes"
|
||||
if [ "$HTTP_CODE" != "200" ]; then
|
||||
echo "ERROR: liteLLM returned HTTP $HTTP_CODE"
|
||||
cat /tmp/llm_response.json
|
||||
exit 1
|
||||
fi
|
||||
if ! jq empty /tmp/llm_response.json 2>/dev/null; then
|
||||
echo "ERROR: Invalid JSON response from liteLLM"
|
||||
cat /tmp/llm_response.json
|
||||
exit 1
|
||||
fi
|
||||
REVIEW=$(jq -r '.choices[0].message.content // empty' /tmp/llm_response.json)
|
||||
if [ -z "$REVIEW" ]; then
|
||||
echo "ERROR: No content in liteLLM response"
|
||||
exit 1
|
||||
fi
|
||||
echo "Review length: ${#REVIEW} chars"
|
||||
echo "$REVIEW" > /tmp/pr_review.txt
|
||||
|
||||
- name: Verify findings against codebase
|
||||
if: steps.analyze.outcome == 'success'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# For each finding that contains a fenced code block under "Evidence:",
|
||||
# grep at least one substantial line of that block against the FULL repository.
|
||||
# Searching the full repo (not just changed files) prevents false UNVERIFIED
|
||||
# tags when the model correctly quotes unchanged files, while still flagging
|
||||
# fabricated code that doesn't exist anywhere in the codebase.
|
||||
python3 - << 'PYEOF'
|
||||
import re, os, subprocess
|
||||
|
||||
review = open('/tmp/pr_review.txt').read()
|
||||
|
||||
# Load ENTIRE tracked repository (all .rs, .ts, .tsx, .yml, .toml, .json files)
|
||||
result = subprocess.run(
|
||||
['git', 'ls-files', '--',
|
||||
'*.rs', '*.ts', '*.tsx', '*.yml', '*.yaml', '*.toml', '*.json', '*.sql'],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
all_tracked = [f.strip() for f in result.stdout.splitlines() if f.strip()]
|
||||
|
||||
all_content_parts = []
|
||||
for path in all_tracked:
|
||||
if os.path.isfile(path):
|
||||
try:
|
||||
all_content_parts.append(open(path).read())
|
||||
except Exception:
|
||||
pass
|
||||
all_content = '\n'.join(all_content_parts)
|
||||
|
||||
def evidence_exists(block: str) -> bool:
|
||||
"""True if ≥1 significant line from the block is found verbatim in changed files."""
|
||||
for raw in block.splitlines():
|
||||
line = raw.lstrip('+-').strip()
|
||||
# Skip blank, very short, pure-comment, or diff-header lines
|
||||
if len(line) < 20:
|
||||
continue
|
||||
if line.startswith(('//','#','/*','*','Fix:','Evidence:','---','+++')):
|
||||
continue
|
||||
if line in all_content:
|
||||
return True
|
||||
return False
|
||||
|
||||
# Split on finding markers; re-join after optional tagging
|
||||
severity_re = re.compile(r'\[(BLOCKER|WARNING|SUGGESTION)\]')
|
||||
|
||||
def tag_if_unverified(finding_text: str) -> str:
|
||||
code_match = re.search(r'```[^\n]*\n(.*?)```', finding_text, re.DOTALL)
|
||||
if code_match and not evidence_exists(code_match.group(1)):
|
||||
# Replace first severity tag with a prefixed version
|
||||
return severity_re.sub(
|
||||
lambda m: f'[{m.group(1)} — ⚠️ UNVERIFIED: evidence not found in PR files]',
|
||||
finding_text, count=1
|
||||
)
|
||||
return finding_text
|
||||
|
||||
# Split review into preamble + individual finding blocks
|
||||
# Each block starts at a severity marker line
|
||||
parts = re.split(r'(?=^\[(?:BLOCKER|WARNING|SUGGESTION)\])', review, flags=re.MULTILINE)
|
||||
result = parts[0] # preamble (Summary, etc.)
|
||||
for block in parts[1:]:
|
||||
result += tag_if_unverified(block)
|
||||
|
||||
open('/tmp/pr_review.txt', 'w').write(result)
|
||||
print(f"Verification complete — {len(parts)-1} finding(s) checked.")
|
||||
PYEOF
|
||||
|
||||
- name: Post review comment
|
||||
if: always() && steps.context.outputs.diff_size != '0'
|
||||
shell: bash
|
||||
env:
|
||||
TF_TOKEN: ${{ secrets.TFT_GITEA_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${TF_TOKEN:-}" ]; then
|
||||
echo "ERROR: TFT_GITEA_TOKEN secret is not set"
|
||||
exit 1
|
||||
fi
|
||||
if [ -f "/tmp/pr_review.txt" ] && [ -s "/tmp/pr_review.txt" ]; then
|
||||
REVIEW_BODY=$(head -c 65536 /tmp/pr_review.txt)
|
||||
BODY=$(jq -n \
|
||||
--arg body "Automated PR Review (qwen3-coder-next via liteLLM):\n\n${REVIEW_BODY}" \
|
||||
'{body: $body, event: "COMMENT"}')
|
||||
else
|
||||
BODY=$(jq -n \
|
||||
'{body: "Automated PR Review could not be completed - LLM analysis failed or produced no output.", event: "COMMENT"}')
|
||||
fi
|
||||
HTTP_CODE=$(curl -s --max-time 30 --connect-timeout 10 \
|
||||
-o /tmp/review_post_response.json -w "%{http_code}" \
|
||||
-X POST "https://gogs.tftsr.com/api/v1/repos/${REPOSITORY}/pulls/${PR_NUMBER}/reviews" \
|
||||
-H "Authorization: Bearer $TF_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$BODY")
|
||||
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Post review HTTP status: $HTTP_CODE"
|
||||
if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ]; then
|
||||
echo "ERROR: Failed to post review (HTTP $HTTP_CODE)"
|
||||
cat /tmp/review_post_response.json
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
shell: bash
|
||||
run: rm -f /tmp/pr_diff.txt /tmp/pr_context.txt /tmp/codebase_index.txt /tmp/prompt.txt /tmp/body.json /tmp/llm_response.json /tmp/pr_review.txt /tmp/review_post_response.json /tmp/pr_files.txt
|
||||
@ -1,186 +0,0 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
rust-fmt-check:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: 172.0.0.29:3000/sarman/trcaa-linux-amd64:rust1.88-node22
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
set -eux
|
||||
git init
|
||||
git remote add origin http://172.0.0.29:3000/sarman/tftsr-devops_investigation.git
|
||||
if [ -n "${GITHUB_SHA:-}" ] && git fetch --depth=1 origin "$GITHUB_SHA"; then
|
||||
echo "Fetched commit SHA: $GITHUB_SHA"
|
||||
elif [ -n "${GITHUB_REF_NAME:-}" ] && git fetch --depth=1 origin "$GITHUB_REF_NAME"; then
|
||||
echo "Fetched ref name: $GITHUB_REF_NAME"
|
||||
elif [ -n "${GITHUB_REF:-}" ]; then
|
||||
REF_NAME="${GITHUB_REF#refs/heads/}"
|
||||
git fetch --depth=1 origin "$REF_NAME"
|
||||
echo "Fetched ref from GITHUB_REF: $REF_NAME"
|
||||
else
|
||||
git fetch --depth=1 origin master
|
||||
echo "Fetched fallback ref: master"
|
||||
fi
|
||||
git checkout FETCH_HEAD
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git/db
|
||||
key: ${{ runner.os }}-cargo-linux-amd64-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-linux-amd64-
|
||||
- name: Install dependencies
|
||||
run: npm install --legacy-peer-deps
|
||||
- name: Update version from Git
|
||||
run: node scripts/update-version.mjs
|
||||
- run: cargo generate-lockfile --manifest-path src-tauri/Cargo.toml
|
||||
- run: cargo fmt --manifest-path src-tauri/Cargo.toml --check
|
||||
|
||||
rust-clippy:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: 172.0.0.29:3000/sarman/trcaa-linux-amd64:rust1.88-node22
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
set -eux
|
||||
git init
|
||||
git remote add origin http://172.0.0.29:3000/sarman/tftsr-devops_investigation.git
|
||||
if [ -n "${GITHUB_SHA:-}" ] && git fetch --depth=1 origin "$GITHUB_SHA"; then
|
||||
echo "Fetched commit SHA: $GITHUB_SHA"
|
||||
elif [ -n "${GITHUB_REF_NAME:-}" ] && git fetch --depth=1 origin "$GITHUB_REF_NAME"; then
|
||||
echo "Fetched ref name: $GITHUB_REF_NAME"
|
||||
elif [ -n "${GITHUB_REF:-}" ]; then
|
||||
REF_NAME="${GITHUB_REF#refs/heads/}"
|
||||
git fetch --depth=1 origin "$REF_NAME"
|
||||
echo "Fetched ref from GITHUB_REF: $REF_NAME"
|
||||
else
|
||||
git fetch --depth=1 origin master
|
||||
echo "Fetched fallback ref: master"
|
||||
fi
|
||||
git checkout FETCH_HEAD
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git/db
|
||||
key: ${{ runner.os }}-cargo-linux-amd64-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-linux-amd64-
|
||||
- run: cargo clippy --manifest-path src-tauri/Cargo.toml -- -D warnings
|
||||
|
||||
rust-tests:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: 172.0.0.29:3000/sarman/trcaa-linux-amd64:rust1.88-node22
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
set -eux
|
||||
git init
|
||||
git remote add origin http://172.0.0.29:3000/sarman/tftsr-devops_investigation.git
|
||||
if [ -n "${GITHUB_SHA:-}" ] && git fetch --depth=1 origin "$GITHUB_SHA"; then
|
||||
echo "Fetched commit SHA: $GITHUB_SHA"
|
||||
elif [ -n "${GITHUB_REF_NAME:-}" ] && git fetch --depth=1 origin "$GITHUB_REF_NAME"; then
|
||||
echo "Fetched ref name: $GITHUB_REF_NAME"
|
||||
elif [ -n "${GITHUB_REF:-}" ]; then
|
||||
REF_NAME="${GITHUB_REF#refs/heads/}"
|
||||
git fetch --depth=1 origin "$REF_NAME"
|
||||
echo "Fetched ref from GITHUB_REF: $REF_NAME"
|
||||
else
|
||||
git fetch --depth=1 origin master
|
||||
echo "Fetched fallback ref: master"
|
||||
fi
|
||||
git checkout FETCH_HEAD
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git/db
|
||||
key: ${{ runner.os }}-cargo-linux-amd64-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-linux-amd64-
|
||||
- run: cargo test --manifest-path src-tauri/Cargo.toml -- --test-threads=1
|
||||
|
||||
frontend-typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node:22-alpine
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
set -eux
|
||||
apk add --no-cache git
|
||||
git init
|
||||
git remote add origin http://172.0.0.29:3000/sarman/tftsr-devops_investigation.git
|
||||
if [ -n "${GITHUB_SHA:-}" ] && git fetch --depth=1 origin "$GITHUB_SHA"; then
|
||||
echo "Fetched commit SHA: $GITHUB_SHA"
|
||||
elif [ -n "${GITHUB_REF_NAME:-}" ] && git fetch --depth=1 origin "$GITHUB_REF_NAME"; then
|
||||
echo "Fetched ref name: $GITHUB_REF_NAME"
|
||||
elif [ -n "${GITHUB_REF:-}" ]; then
|
||||
REF_NAME="${GITHUB_REF#refs/heads/}"
|
||||
git fetch --depth=1 origin "$REF_NAME"
|
||||
echo "Fetched ref from GITHUB_REF: $REF_NAME"
|
||||
else
|
||||
git fetch --depth=1 origin master
|
||||
echo "Fetched fallback ref: master"
|
||||
fi
|
||||
git checkout FETCH_HEAD
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-npm-
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npx tsc --noEmit
|
||||
|
||||
frontend-tests:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node:22-alpine
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
set -eux
|
||||
apk add --no-cache git
|
||||
git init
|
||||
git remote add origin http://172.0.0.29:3000/sarman/tftsr-devops_investigation.git
|
||||
if [ -n "${GITHUB_SHA:-}" ] && git fetch --depth=1 origin "$GITHUB_SHA"; then
|
||||
echo "Fetched commit SHA: $GITHUB_SHA"
|
||||
elif [ -n "${GITHUB_REF_NAME:-}" ] && git fetch --depth=1 origin "$GITHUB_REF_NAME"; then
|
||||
echo "Fetched ref name: $GITHUB_REF_NAME"
|
||||
elif [ -n "${GITHUB_REF:-}" ]; then
|
||||
REF_NAME="${GITHUB_REF#refs/heads/}"
|
||||
git fetch --depth=1 origin "$REF_NAME"
|
||||
echo "Fetched ref from GITHUB_REF: $REF_NAME"
|
||||
else
|
||||
git fetch --depth=1 origin master
|
||||
echo "Fetched fallback ref: master"
|
||||
fi
|
||||
git checkout FETCH_HEAD
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-npm-
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npm run test:run
|
||||
9
.github/CODEOWNERS
vendored
Normal file
9
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
# All files require review from owner and GitHub Copilot
|
||||
* @Shaun-Arman-VFK387_moto @github-copilot
|
||||
|
||||
# Rust backend
|
||||
src-tauri/ @Shaun-Arman-VFK387_moto @github-copilot
|
||||
|
||||
# CI/CD pipelines and Docker build configs
|
||||
.github/workflows/ @Shaun-Arman-VFK387_moto @github-copilot
|
||||
.docker/ @Shaun-Arman-VFK387_moto @github-copilot
|
||||
71
.github/workflows/build-images.yml
vendored
71
.github/workflows/build-images.yml
vendored
@ -1,26 +1,20 @@
|
||||
name: Build CI Docker Images
|
||||
|
||||
# Rebuilds the pre-baked builder images and pushes them to the local Gitea
|
||||
# container registry (172.0.0.29:3000).
|
||||
# Rebuilds the pre-baked builder images and pushes them to ghcr.io.
|
||||
#
|
||||
# WHEN TO RUN:
|
||||
# - Automatically: whenever a Dockerfile under .docker/ changes on master.
|
||||
# - Automatically: whenever a Dockerfile under .docker/ changes on main.
|
||||
# - Manually: via workflow_dispatch (e.g. first-time setup, forced rebuild).
|
||||
#
|
||||
# ONE-TIME SERVER PREREQUISITE (run once on 172.0.0.29 before first use):
|
||||
# echo '{"insecure-registries":["172.0.0.29:3000"]}' \
|
||||
# | sudo tee /etc/docker/daemon.json
|
||||
# sudo systemctl restart docker
|
||||
#
|
||||
# Images produced:
|
||||
# 172.0.0.29:3000/sarman/trcaa-linux-amd64:rust1.88-node22
|
||||
# 172.0.0.29:3000/sarman/trcaa-windows-cross:rust1.88-node22
|
||||
# 172.0.0.29:3000/sarman/trcaa-linux-arm64:rust1.88-node22
|
||||
# ghcr.io/msicie/trcaa-linux-amd64:rust1.88-node22
|
||||
# ghcr.io/msicie/trcaa-windows-cross:rust1.88-node22
|
||||
# ghcr.io/msicie/trcaa-linux-arm64:rust1.88-node22
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
paths:
|
||||
- '.docker/**'
|
||||
workflow_dispatch:
|
||||
@ -30,66 +24,61 @@ concurrency:
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
REGISTRY: 172.0.0.29:3000
|
||||
REGISTRY_USER: sarman
|
||||
REGISTRY: ghcr.io
|
||||
REGISTRY_OWNER: msicie
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
linux-amd64:
|
||||
runs-on: linux-amd64
|
||||
container:
|
||||
image: docker:24-cli
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Log in to ghcr.io
|
||||
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||
- name: Build and push linux-amd64 builder
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
echo "$RELEASE_TOKEN" | docker login $REGISTRY -u $REGISTRY_USER --password-stdin
|
||||
docker build \
|
||||
-t $REGISTRY/$REGISTRY_USER/trcaa-linux-amd64:rust1.88-node22 \
|
||||
-t $REGISTRY/$REGISTRY_OWNER/trcaa-linux-amd64:rust1.88-node22 \
|
||||
-f .docker/Dockerfile.linux-amd64 .
|
||||
docker push $REGISTRY/$REGISTRY_USER/trcaa-linux-amd64:rust1.88-node22
|
||||
echo "✓ Pushed $REGISTRY/$REGISTRY_USER/trcaa-linux-amd64:rust1.88-node22"
|
||||
docker push $REGISTRY/$REGISTRY_OWNER/trcaa-linux-amd64:rust1.88-node22
|
||||
echo "✓ Pushed $REGISTRY/$REGISTRY_OWNER/trcaa-linux-amd64:rust1.88-node22"
|
||||
|
||||
windows-cross:
|
||||
runs-on: linux-amd64
|
||||
container:
|
||||
image: docker:24-cli
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Log in to ghcr.io
|
||||
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||
- name: Build and push windows-cross builder
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
echo "$RELEASE_TOKEN" | docker login $REGISTRY -u $REGISTRY_USER --password-stdin
|
||||
docker build \
|
||||
-t $REGISTRY/$REGISTRY_USER/trcaa-windows-cross:rust1.88-node22 \
|
||||
-t $REGISTRY/$REGISTRY_OWNER/trcaa-windows-cross:rust1.88-node22 \
|
||||
-f .docker/Dockerfile.windows-cross .
|
||||
docker push $REGISTRY/$REGISTRY_USER/trcaa-windows-cross:rust1.88-node22
|
||||
echo "✓ Pushed $REGISTRY/$REGISTRY_USER/trcaa-windows-cross:rust1.88-node22"
|
||||
docker push $REGISTRY/$REGISTRY_OWNER/trcaa-windows-cross:rust1.88-node22
|
||||
echo "✓ Pushed $REGISTRY/$REGISTRY_OWNER/trcaa-windows-cross:rust1.88-node22"
|
||||
|
||||
linux-arm64:
|
||||
runs-on: linux-amd64
|
||||
container:
|
||||
image: docker:24-cli
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Log in to ghcr.io
|
||||
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||
- name: Build and push linux-arm64 builder
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
echo "$RELEASE_TOKEN" | docker login $REGISTRY -u $REGISTRY_USER --password-stdin
|
||||
docker build \
|
||||
-t $REGISTRY/$REGISTRY_USER/trcaa-linux-arm64:rust1.88-node22 \
|
||||
-t $REGISTRY/$REGISTRY_OWNER/trcaa-linux-arm64:rust1.88-node22 \
|
||||
-f .docker/Dockerfile.linux-arm64 .
|
||||
docker push $REGISTRY/$REGISTRY_USER/trcaa-linux-arm64:rust1.88-node22
|
||||
echo "✓ Pushed $REGISTRY/$REGISTRY_USER/trcaa-linux-arm64:rust1.88-node22"
|
||||
docker push $REGISTRY/$REGISTRY_OWNER/trcaa-linux-arm64:rust1.88-node22
|
||||
echo "✓ Pushed $REGISTRY/$REGISTRY_OWNER/trcaa-linux-arm64:rust1.88-node22"
|
||||
|
||||
611
.github/workflows/release.yml
vendored
611
.github/workflows/release.yml
vendored
@ -1,43 +1,68 @@
|
||||
name: Auto Tag
|
||||
name: Release
|
||||
|
||||
# Runs on every merge to master — reads the latest semver tag, increments
|
||||
# the patch version, pushes a new tag, then runs release builds in this workflow.
|
||||
# workflow_dispatch allows manual triggering when Gitea drops a push event.
|
||||
# Runs on every merge to main — reads the latest semver tag, increments
|
||||
# the patch version, pushes a new tag, generates a changelog, then builds
|
||||
# multi-platform release artifacts and uploads them to GitHub Releases.
|
||||
# workflow_dispatch allows manual triggering.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: auto-tag-master
|
||||
group: release-main
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: read
|
||||
|
||||
jobs:
|
||||
autotag:
|
||||
runs-on: linux-amd64
|
||||
container:
|
||||
image: alpine:latest
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
release_tag: ${{ steps.bump.outputs.release_tag }}
|
||||
steps:
|
||||
- name: Checkout (full history + all tags)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Bump patch version and create tag
|
||||
id: bump
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
apk add --no-cache curl jq git
|
||||
|
||||
API="http://172.0.0.29:3000/api/v1/repos/$GITHUB_REPOSITORY"
|
||||
# Read the version declared in Cargo.toml
|
||||
CARGO_VERSION=$(grep '^version' src-tauri/Cargo.toml | head -1 | sed 's/version = "//;s/"//')
|
||||
CARGO_TAG="v${CARGO_VERSION}"
|
||||
echo "Cargo.toml declares: $CARGO_TAG"
|
||||
|
||||
# Get the latest clean semver tag (vX.Y.Z only, ignore rc/test suffixes)
|
||||
LATEST=$(curl -s "$API/tags?limit=50" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | \
|
||||
jq -r '.[].name' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | \
|
||||
sort -V | tail -1)
|
||||
# Get the latest clean semver tag (vX.Y.Z only)
|
||||
LATEST=$(git tag --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1 || echo "")
|
||||
echo "Latest git tag: ${LATEST:-none}"
|
||||
|
||||
# Version resolution:
|
||||
# 1. Cargo.toml > latest tag → use Cargo.toml (major/minor bump)
|
||||
# 2. Cargo.toml == latest tag → reuse for builds (already tagged)
|
||||
# 3. Cargo.toml < latest tag → auto-increment patch on latest tag
|
||||
if [ -z "$LATEST" ]; then
|
||||
NEXT="v0.1.0"
|
||||
NEXT="$CARGO_TAG"
|
||||
elif [ "$(printf '%s\n' "$LATEST" "$CARGO_TAG" | sort -V | tail -1)" = "$CARGO_TAG" ]; then
|
||||
NEXT="$CARGO_TAG"
|
||||
if [ "$CARGO_TAG" = "$LATEST" ]; then
|
||||
echo "Cargo.toml matches latest tag — reusing $NEXT for builds"
|
||||
else
|
||||
echo "Cargo.toml version $CARGO_TAG is ahead of $LATEST — using Cargo.toml"
|
||||
fi
|
||||
else
|
||||
MAJOR=$(echo "$LATEST" | cut -d. -f1 | tr -d 'v')
|
||||
MINOR=$(echo "$LATEST" | cut -d. -f2)
|
||||
@ -47,53 +72,136 @@ jobs:
|
||||
|
||||
echo "Latest tag: ${LATEST:-none} → Next: $NEXT"
|
||||
|
||||
# Create and push the tag via git.
|
||||
git init
|
||||
git remote add origin "http://oauth2:${RELEASE_TOKEN}@172.0.0.29:3000/${GITHUB_REPOSITORY}.git"
|
||||
git fetch --depth=1 origin "$GITHUB_SHA"
|
||||
git checkout FETCH_HEAD
|
||||
git config user.name "gitea-actions[bot]"
|
||||
git config user.email "gitea-actions@local"
|
||||
|
||||
if git ls-remote --exit-code --tags origin "refs/tags/$NEXT" >/dev/null 2>&1; then
|
||||
echo "Tag $NEXT already exists; skipping."
|
||||
exit 0
|
||||
echo "Tag $NEXT already exists; builds will target this tag."
|
||||
else
|
||||
git tag -a "$NEXT" -m "Release $NEXT"
|
||||
git push origin "refs/tags/$NEXT"
|
||||
echo "Tag $NEXT pushed successfully"
|
||||
fi
|
||||
|
||||
git tag -a "$NEXT" -m "Release $NEXT"
|
||||
git push origin "refs/tags/$NEXT"
|
||||
echo "release_tag=$NEXT" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "Tag $NEXT pushed successfully"
|
||||
changelog:
|
||||
needs: autotag
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout (full history + all tags)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Install git-cliff
|
||||
run: |
|
||||
set -eu
|
||||
CLIFF_VER="2.7.0"
|
||||
curl -fsSL \
|
||||
"https://github.com/orhun/git-cliff/releases/download/v${CLIFF_VER}/git-cliff-${CLIFF_VER}-x86_64-unknown-linux-musl.tar.gz" \
|
||||
| tar -xz --strip-components=1 -C /usr/local/bin \
|
||||
"git-cliff-${CLIFF_VER}/git-cliff"
|
||||
|
||||
- name: Generate changelog
|
||||
env:
|
||||
RELEASE_TAG: ${{ needs.autotag.outputs.release_tag }}
|
||||
run: |
|
||||
set -eu
|
||||
CURRENT_TAG="${RELEASE_TAG}"
|
||||
echo "Building changelog for $CURRENT_TAG"
|
||||
|
||||
if ! git rev-parse "refs/tags/${CURRENT_TAG}" >/dev/null 2>&1; then
|
||||
echo "ERROR: tag ${CURRENT_TAG} not found locally after fetch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git-cliff --config cliff.toml --output CHANGELOG.md
|
||||
PREV_TAG=$(git tag --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \
|
||||
| grep -v "^${CURRENT_TAG}$" | head -1 || echo "")
|
||||
if [ -n "$PREV_TAG" ]; then
|
||||
git-cliff --config cliff.toml --tag "$CURRENT_TAG" --strip all > /tmp/release_body.md || true
|
||||
else
|
||||
echo "No previous tag found, generating from git commits"
|
||||
git log --pretty=format:"- %s" > /tmp/release_body.md || true
|
||||
fi
|
||||
echo "=== Release body preview ==="
|
||||
cat /tmp/release_body.md
|
||||
|
||||
- name: Create or update GitHub release
|
||||
env:
|
||||
RELEASE_TAG: ${{ needs.autotag.outputs.release_tag }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
TAG="${RELEASE_TAG}"
|
||||
BODY=$(cat /tmp/release_body.md)
|
||||
|
||||
if gh release view "$TAG" >/dev/null 2>&1; then
|
||||
echo "Updating existing release $TAG..."
|
||||
gh release edit "$TAG" --notes "$BODY"
|
||||
echo "✓ Release body updated"
|
||||
else
|
||||
echo "Creating release $TAG..."
|
||||
gh release create "$TAG" \
|
||||
--title "TFTSR $TAG" \
|
||||
--notes "$BODY"
|
||||
echo "✓ Release created"
|
||||
fi
|
||||
|
||||
- name: Commit CHANGELOG.md to main
|
||||
env:
|
||||
RELEASE_TAG: ${{ needs.autotag.outputs.release_tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG="${RELEASE_TAG}"
|
||||
if ! echo "$TAG" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||
echo "ERROR: Unexpected tag format: $TAG"
|
||||
exit 1
|
||||
fi
|
||||
git add CHANGELOG.md
|
||||
if git diff --staged --quiet; then
|
||||
echo "No CHANGELOG.md changes to commit"
|
||||
else
|
||||
git commit -m "chore: update CHANGELOG.md for ${TAG} [skip ci]"
|
||||
git push origin HEAD:main
|
||||
echo "✓ CHANGELOG.md committed to main"
|
||||
fi
|
||||
|
||||
- name: Upload CHANGELOG.md as release asset
|
||||
env:
|
||||
RELEASE_TAG: ${{ needs.autotag.outputs.release_tag }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
TAG="${RELEASE_TAG}"
|
||||
# Remove existing asset if present to allow re-upload
|
||||
gh release delete-asset "$TAG" CHANGELOG.md --yes 2>/dev/null || true
|
||||
gh release upload "$TAG" CHANGELOG.md
|
||||
echo "✓ CHANGELOG.md uploaded"
|
||||
|
||||
wiki-sync:
|
||||
runs-on: linux-amd64
|
||||
container:
|
||||
image: alpine:latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: apk add --no-cache git
|
||||
|
||||
- name: Checkout main repository
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config --global user.email "actions@gitea.local"
|
||||
git config --global user.name "Gitea Actions"
|
||||
git config --global credential.helper ''
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --global user.name "github-actions[bot]"
|
||||
|
||||
- name: Clone and sync wiki
|
||||
env:
|
||||
WIKI_TOKEN: ${{ secrets.Wiki }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
cd /tmp
|
||||
if [ -n "$WIKI_TOKEN" ]; then
|
||||
WIKI_URL="http://${WIKI_TOKEN}@172.0.0.29:3000/sarman/tftsr-devops_investigation.wiki.git"
|
||||
else
|
||||
WIKI_URL="http://172.0.0.29:3000/sarman/tftsr-devops_investigation.wiki.git"
|
||||
fi
|
||||
WIKI_URL="https://x-access-token:${GH_TOKEN}@github.com/msicie/apollo_nxt-trcaa.wiki.git"
|
||||
|
||||
if ! git clone "$WIKI_URL" wiki 2>/dev/null; then
|
||||
echo "Wiki doesn't exist yet, creating initial structure..."
|
||||
@ -115,11 +223,10 @@ jobs:
|
||||
git add -A
|
||||
if ! git diff --staged --quiet; then
|
||||
git commit -m "docs: sync from docs/wiki/ at commit ${GITHUB_SHA:0:8}"
|
||||
echo "Pushing to wiki..."
|
||||
if git push origin master; then
|
||||
echo "✓ Wiki successfully synced"
|
||||
else
|
||||
echo "⚠ Wiki push failed - check token permissions"
|
||||
echo "⚠ Wiki push failed"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
@ -128,102 +235,91 @@ jobs:
|
||||
|
||||
build-linux-amd64:
|
||||
needs: autotag
|
||||
runs-on: linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: rust:1.88-slim
|
||||
image: ghcr.io/msicie/trcaa-linux-amd64:rust1.88-node22
|
||||
credentials:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt-get update -qq && apt-get install -y -qq \
|
||||
libwebkit2gtk-4.1-dev libssl-dev libgtk-3-dev \
|
||||
libayatana-appindicator3-dev librsvg2-dev patchelf \
|
||||
pkg-config curl perl jq
|
||||
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
||||
apt-get install -y nodejs
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git/db
|
||||
key: ${{ runner.os }}-cargo-linux-amd64-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-linux-amd64-
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-npm-
|
||||
- name: Build
|
||||
env:
|
||||
APPIMAGE_EXTRACT_AND_RUN: "1"
|
||||
run: |
|
||||
npm ci --legacy-peer-deps
|
||||
rustup target add x86_64-unknown-linux-gnu
|
||||
CI=true npx tauri build --target x86_64-unknown-linux-gnu
|
||||
- name: Upload artifacts
|
||||
- name: Upload artifacts to GitHub release
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
RELEASE_TAG: ${{ needs.autotag.outputs.release_tag }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
API="http://172.0.0.29:3000/api/v1/repos/$GITHUB_REPOSITORY"
|
||||
TAG=$(curl -s "$API/tags?limit=50" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | \
|
||||
jq -r '.[].name' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | \
|
||||
sort -V | tail -1 || true)
|
||||
if [ -z "$TAG" ]; then
|
||||
echo "ERROR: Could not resolve release tag from repository tags."
|
||||
exit 1
|
||||
fi
|
||||
echo "Creating release for $TAG..."
|
||||
curl -sf -X POST "$API/releases" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\":\"$TAG\",\"name\":\"TFTSR $TAG\",\"body\":\"Release $TAG\",\"draft\":false}" || true
|
||||
RELEASE_ID=$(curl -sf "$API/releases/tags/$TAG" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | jq -r '.id')
|
||||
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then
|
||||
echo "ERROR: Failed to get release ID for $TAG"
|
||||
exit 1
|
||||
fi
|
||||
echo "Release ID: $RELEASE_ID"
|
||||
TAG="${RELEASE_TAG}"
|
||||
ARTIFACTS=$(find src-tauri/target/x86_64-unknown-linux-gnu/release/bundle -type f \
|
||||
\( -name "*.deb" -o -name "*.rpm" -o -name "*.AppImage" \))
|
||||
\( -name "*.deb" -o -name "*.rpm" \))
|
||||
if [ -z "$ARTIFACTS" ]; then
|
||||
echo "ERROR: No Linux amd64 artifacts were found to upload."
|
||||
echo "ERROR: No Linux amd64 artifacts found."
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$ARTIFACTS" | while IFS= read -r f; do
|
||||
NAME=$(basename "$f")
|
||||
UPLOAD_NAME="linux-amd64-$NAME"
|
||||
echo "Uploading $UPLOAD_NAME..."
|
||||
EXISTING_IDS=$(curl -sf "$API/releases/$RELEASE_ID" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
| jq -r --arg name "$UPLOAD_NAME" '.assets[]? | select(.name == $name) | .id')
|
||||
if [ -n "$EXISTING_IDS" ]; then
|
||||
printf '%s\n' "$EXISTING_IDS" | while IFS= read -r id; do
|
||||
[ -n "$id" ] || continue
|
||||
echo "Deleting existing asset id=$id name=$UPLOAD_NAME before upload..."
|
||||
curl -sf -X DELETE "$API/releases/$RELEASE_ID/assets/$id" \
|
||||
-H "Authorization: token $RELEASE_TOKEN"
|
||||
done
|
||||
fi
|
||||
RESP_FILE=$(mktemp)
|
||||
HTTP_CODE=$(curl -sS -o "$RESP_FILE" -w "%{http_code}" -X POST "$API/releases/$RELEASE_ID/assets" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-F "attachment=@$f;filename=$UPLOAD_NAME")
|
||||
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||
echo "✓ Uploaded $UPLOAD_NAME"
|
||||
else
|
||||
echo "✗ Upload failed for $UPLOAD_NAME (HTTP $HTTP_CODE)"
|
||||
python -c 'import pathlib,sys;print(pathlib.Path(sys.argv[1]).read_text(errors="replace")[:2000])' "$RESP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
NAME="linux-amd64-$(basename "$f")"
|
||||
echo "Uploading $NAME..."
|
||||
gh release delete-asset "$TAG" "$NAME" --yes 2>/dev/null || true
|
||||
gh release upload "$TAG" "$f#$NAME"
|
||||
echo "✓ Uploaded $NAME"
|
||||
done
|
||||
|
||||
build-windows-amd64:
|
||||
needs: autotag
|
||||
runs-on: linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: rust:1.88-slim
|
||||
image: ghcr.io/msicie/trcaa-windows-cross:rust1.88-node22
|
||||
credentials:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt-get update -qq && apt-get install -y -qq mingw-w64 curl nsis perl make jq
|
||||
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
||||
apt-get install -y nodejs
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git/db
|
||||
key: ${{ runner.os }}-cargo-windows-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-windows-
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-npm-
|
||||
- name: Build
|
||||
env:
|
||||
CC_x86_64_pc_windows_gnu: x86_64-w64-mingw32-gcc
|
||||
@ -234,65 +330,26 @@ jobs:
|
||||
OPENSSL_STATIC: "1"
|
||||
run: |
|
||||
npm ci --legacy-peer-deps
|
||||
rustup target add x86_64-pc-windows-gnu
|
||||
CI=true npx tauri build --target x86_64-pc-windows-gnu
|
||||
- name: Upload artifacts
|
||||
- name: Upload artifacts to GitHub release
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
RELEASE_TAG: ${{ needs.autotag.outputs.release_tag }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
API="http://172.0.0.29:3000/api/v1/repos/$GITHUB_REPOSITORY"
|
||||
TAG=$(curl -s "$API/tags?limit=50" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | \
|
||||
jq -r '.[].name' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | \
|
||||
sort -V | tail -1 || true)
|
||||
if [ -z "$TAG" ]; then
|
||||
echo "ERROR: Could not resolve release tag from repository tags."
|
||||
exit 1
|
||||
fi
|
||||
echo "Creating release for $TAG..."
|
||||
curl -sf -X POST "$API/releases" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\":\"$TAG\",\"name\":\"TFTSR $TAG\",\"body\":\"Release $TAG\",\"draft\":false}" || true
|
||||
RELEASE_ID=$(curl -sf "$API/releases/tags/$TAG" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | jq -r '.id')
|
||||
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then
|
||||
echo "ERROR: Failed to get release ID for $TAG"
|
||||
exit 1
|
||||
fi
|
||||
echo "Release ID: $RELEASE_ID"
|
||||
TAG="${RELEASE_TAG}"
|
||||
ARTIFACTS=$(find src-tauri/target/x86_64-pc-windows-gnu/release/bundle -type f \
|
||||
\( -name "*.exe" -o -name "*.msi" \) 2>/dev/null)
|
||||
if [ -z "$ARTIFACTS" ]; then
|
||||
echo "ERROR: No Windows amd64 artifacts were found to upload."
|
||||
echo "ERROR: No Windows amd64 artifacts found."
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$ARTIFACTS" | while IFS= read -r f; do
|
||||
NAME=$(basename "$f")
|
||||
NAME="windows-amd64-$(basename "$f")"
|
||||
echo "Uploading $NAME..."
|
||||
EXISTING_IDS=$(curl -sf "$API/releases/$RELEASE_ID" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
| jq -r --arg name "$NAME" '.assets[]? | select(.name == $name) | .id')
|
||||
if [ -n "$EXISTING_IDS" ]; then
|
||||
printf '%s\n' "$EXISTING_IDS" | while IFS= read -r id; do
|
||||
[ -n "$id" ] || continue
|
||||
echo "Deleting existing asset id=$id name=$NAME before upload..."
|
||||
curl -sf -X DELETE "$API/releases/$RELEASE_ID/assets/$id" \
|
||||
-H "Authorization: token $RELEASE_TOKEN"
|
||||
done
|
||||
fi
|
||||
RESP_FILE=$(mktemp)
|
||||
HTTP_CODE=$(curl -sS -o "$RESP_FILE" -w "%{http_code}" -X POST "$API/releases/$RELEASE_ID/assets" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-F "attachment=@$f;filename=$NAME")
|
||||
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||
echo "✓ Uploaded $NAME"
|
||||
else
|
||||
echo "✗ Upload failed for $NAME (HTTP $HTTP_CODE)"
|
||||
python -c 'import pathlib,sys;print(pathlib.Path(sys.argv[1]).read_text(errors="replace")[:2000])' "$RESP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
gh release delete-asset "$TAG" "$NAME" --yes 2>/dev/null || true
|
||||
gh release upload "$TAG" "$f#$NAME"
|
||||
echo "✓ Uploaded $NAME"
|
||||
done
|
||||
|
||||
build-macos-arm64:
|
||||
@ -320,112 +377,101 @@ jobs:
|
||||
mkdir -p src-tauri/target/aarch64-apple-darwin/release/bundle/dmg
|
||||
DMG=src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/${APP_NAME}.dmg
|
||||
hdiutil create -volname "$APP_NAME" -srcfolder "$APP" -ov -format UDZO "$DMG"
|
||||
- name: Upload artifacts
|
||||
- name: Upload artifacts to GitHub release
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
RELEASE_TAG: ${{ needs.autotag.outputs.release_tag }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
API="http://172.0.0.29:3000/api/v1/repos/$GITHUB_REPOSITORY"
|
||||
TAG=$(curl -s "$API/tags?limit=50" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | \
|
||||
jq -r '.[].name' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | \
|
||||
sort -V | tail -1 || true)
|
||||
if [ -z "$TAG" ]; then
|
||||
echo "ERROR: Could not resolve release tag from repository tags."
|
||||
exit 1
|
||||
fi
|
||||
echo "Creating release for $TAG..."
|
||||
curl -sf -X POST "$API/releases" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\":\"$TAG\",\"name\":\"TFTSR $TAG\",\"body\":\"Release $TAG\",\"draft\":false}" || true
|
||||
RELEASE_ID=$(curl -sf "$API/releases/tags/$TAG" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | jq -r '.id')
|
||||
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then
|
||||
echo "ERROR: Failed to get release ID for $TAG"
|
||||
exit 1
|
||||
fi
|
||||
echo "Release ID: $RELEASE_ID"
|
||||
TAG="${RELEASE_TAG}"
|
||||
ARTIFACTS=$(find src-tauri/target/aarch64-apple-darwin/release/bundle -type f -name "*.dmg")
|
||||
if [ -z "$ARTIFACTS" ]; then
|
||||
echo "ERROR: No macOS arm64 DMG artifacts were found to upload."
|
||||
echo "ERROR: No macOS arm64 DMG artifacts found."
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$ARTIFACTS" | while IFS= read -r f; do
|
||||
NAME=$(basename "$f")
|
||||
NAME="macos-arm64-$(basename "$f")"
|
||||
echo "Uploading $NAME..."
|
||||
EXISTING_IDS=$(curl -sf "$API/releases/$RELEASE_ID" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
| jq -r --arg name "$NAME" '.assets[]? | select(.name == $name) | .id')
|
||||
if [ -n "$EXISTING_IDS" ]; then
|
||||
printf '%s\n' "$EXISTING_IDS" | while IFS= read -r id; do
|
||||
[ -n "$id" ] || continue
|
||||
echo "Deleting existing asset id=$id name=$NAME before upload..."
|
||||
curl -sf -X DELETE "$API/releases/$RELEASE_ID/assets/$id" \
|
||||
-H "Authorization: token $RELEASE_TOKEN"
|
||||
done
|
||||
fi
|
||||
RESP_FILE=$(mktemp)
|
||||
HTTP_CODE=$(curl -sS -o "$RESP_FILE" -w "%{http_code}" -X POST "$API/releases/$RELEASE_ID/assets" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-F "attachment=@$f;filename=$NAME")
|
||||
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||
echo "✓ Uploaded $NAME"
|
||||
else
|
||||
echo "✗ Upload failed for $NAME (HTTP $HTTP_CODE)"
|
||||
python -c 'import pathlib,sys;print(pathlib.Path(sys.argv[1]).read_text(errors="replace")[:2000])' "$RESP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
gh release delete-asset "$TAG" "$NAME" --yes 2>/dev/null || true
|
||||
gh release upload "$TAG" "$f#$NAME"
|
||||
echo "✓ Uploaded $NAME"
|
||||
done
|
||||
|
||||
build-linux-arm64:
|
||||
build-macos-intel:
|
||||
needs: autotag
|
||||
runs-on: linux-amd64
|
||||
container:
|
||||
image: ubuntu:22.04
|
||||
runs-on: macos-13
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Install dependencies
|
||||
- name: Build
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
MACOSX_DEPLOYMENT_TARGET: "10.15"
|
||||
run: |
|
||||
# Step 1: Host tools + cross-compiler (all amd64, no multiarch yet)
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq curl git gcc g++ make patchelf pkg-config perl jq \
|
||||
gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
|
||||
npm ci --legacy-peer-deps
|
||||
rustup target add x86_64-apple-darwin
|
||||
CI=true npx tauri build --target x86_64-apple-darwin --bundles app
|
||||
APP=$(find src-tauri/target/x86_64-apple-darwin/release/bundle/macos -maxdepth 1 -type d -name "*.app" | head -n 1)
|
||||
if [ -z "$APP" ]; then
|
||||
echo "ERROR: Could not find macOS app bundle"
|
||||
exit 1
|
||||
fi
|
||||
APP_NAME=$(basename "$APP" .app)
|
||||
codesign --deep --force --sign - "$APP"
|
||||
mkdir -p src-tauri/target/x86_64-apple-darwin/release/bundle/dmg
|
||||
DMG=src-tauri/target/x86_64-apple-darwin/release/bundle/dmg/${APP_NAME}.dmg
|
||||
hdiutil create -volname "$APP_NAME" -srcfolder "$APP" -ov -format UDZO "$DMG"
|
||||
- name: Upload artifacts to GitHub release
|
||||
env:
|
||||
RELEASE_TAG: ${{ needs.autotag.outputs.release_tag }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
TAG="${RELEASE_TAG}"
|
||||
ARTIFACTS=$(find src-tauri/target/x86_64-apple-darwin/release/bundle -type f -name "*.dmg")
|
||||
if [ -z "$ARTIFACTS" ]; then
|
||||
echo "ERROR: No macOS Intel DMG artifacts found."
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$ARTIFACTS" | while IFS= read -r f; do
|
||||
NAME="macos-intel-$(basename "$f")"
|
||||
echo "Uploading $NAME..."
|
||||
gh release delete-asset "$TAG" "$NAME" --yes 2>/dev/null || true
|
||||
gh release upload "$TAG" "$f#$NAME"
|
||||
echo "✓ Uploaded $NAME"
|
||||
done
|
||||
|
||||
# Step 2: Multiarch — Ubuntu uses ports.ubuntu.com for arm64,
|
||||
# keeping it on a separate mirror from amd64 (archive.ubuntu.com).
|
||||
# This avoids the binary-all index duplication and -dev package
|
||||
# conflicts that plagued the Debian single-mirror approach.
|
||||
dpkg --add-architecture arm64
|
||||
sed -i 's|^deb http://archive.ubuntu.com|deb [arch=amd64] http://archive.ubuntu.com|g' /etc/apt/sources.list
|
||||
sed -i 's|^deb http://security.ubuntu.com|deb [arch=amd64] http://security.ubuntu.com|g' /etc/apt/sources.list
|
||||
printf '%s\n' \
|
||||
'deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted universe multiverse' \
|
||||
'deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted universe multiverse' \
|
||||
'deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted universe multiverse' \
|
||||
> /etc/apt/sources.list.d/arm64-ports.list
|
||||
apt-get update -qq
|
||||
|
||||
# Step 3: ARM64 dev libs — libayatana omitted (no tray icon in this app)
|
||||
apt-get install -y -qq \
|
||||
libwebkit2gtk-4.1-dev:arm64 \
|
||||
libssl-dev:arm64 \
|
||||
libgtk-3-dev:arm64 \
|
||||
librsvg2-dev:arm64
|
||||
|
||||
# Step 4: Node.js
|
||||
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
||||
apt-get install -y nodejs
|
||||
|
||||
# Step 5: Rust (not pre-installed in ubuntu:22.04)
|
||||
# source "$HOME/.cargo/env" in the Build step handles PATH — no GITHUB_PATH needed
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
|
||||
--default-toolchain 1.88.0 --profile minimal --no-modify-path
|
||||
build-linux-arm64:
|
||||
needs: autotag
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/msicie/trcaa-linux-arm64:rust1.88-node22
|
||||
credentials:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git/db
|
||||
key: ${{ runner.os }}-cargo-arm64-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-arm64-
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-npm-
|
||||
- name: Build
|
||||
env:
|
||||
CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc
|
||||
@ -439,66 +485,25 @@ jobs:
|
||||
OPENSSL_STATIC: "1"
|
||||
APPIMAGE_EXTRACT_AND_RUN: "1"
|
||||
run: |
|
||||
. "$HOME/.cargo/env"
|
||||
npm ci --legacy-peer-deps
|
||||
rustup target add aarch64-unknown-linux-gnu
|
||||
CI=true npx tauri build --target aarch64-unknown-linux-gnu --bundles deb,rpm
|
||||
- name: Upload artifacts
|
||||
- name: Upload artifacts to GitHub release
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
RELEASE_TAG: ${{ needs.autotag.outputs.release_tag }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
API="http://172.0.0.29:3000/api/v1/repos/$GITHUB_REPOSITORY"
|
||||
TAG=$(curl -s "$API/tags?limit=50" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | \
|
||||
jq -r '.[].name' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | \
|
||||
sort -V | tail -1 || true)
|
||||
if [ -z "$TAG" ]; then
|
||||
echo "ERROR: Could not resolve release tag from repository tags."
|
||||
exit 1
|
||||
fi
|
||||
echo "Creating release for $TAG..."
|
||||
curl -sf -X POST "$API/releases" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\":\"$TAG\",\"name\":\"TFTSR $TAG\",\"body\":\"Release $TAG\",\"draft\":false}" || true
|
||||
RELEASE_ID=$(curl -sf "$API/releases/tags/$TAG" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" | jq -r '.id')
|
||||
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then
|
||||
echo "ERROR: Failed to get release ID for $TAG"
|
||||
exit 1
|
||||
fi
|
||||
echo "Release ID: $RELEASE_ID"
|
||||
TAG="${RELEASE_TAG}"
|
||||
ARTIFACTS=$(find src-tauri/target/aarch64-unknown-linux-gnu/release/bundle -type f \
|
||||
\( -name "*.deb" -o -name "*.rpm" -o -name "*.AppImage" \))
|
||||
if [ -z "$ARTIFACTS" ]; then
|
||||
echo "ERROR: No Linux arm64 artifacts were found to upload."
|
||||
echo "ERROR: No Linux arm64 artifacts found."
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$ARTIFACTS" | while IFS= read -r f; do
|
||||
NAME=$(basename "$f")
|
||||
UPLOAD_NAME="linux-arm64-$NAME"
|
||||
echo "Uploading $UPLOAD_NAME..."
|
||||
EXISTING_IDS=$(curl -sf "$API/releases/$RELEASE_ID" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
| jq -r --arg name "$UPLOAD_NAME" '.assets[]? | select(.name == $name) | .id')
|
||||
if [ -n "$EXISTING_IDS" ]; then
|
||||
printf '%s\n' "$EXISTING_IDS" | while IFS= read -r id; do
|
||||
[ -n "$id" ] || continue
|
||||
echo "Deleting existing asset id=$id name=$UPLOAD_NAME before upload..."
|
||||
curl -sf -X DELETE "$API/releases/$RELEASE_ID/assets/$id" \
|
||||
-H "Authorization: token $RELEASE_TOKEN"
|
||||
done
|
||||
fi
|
||||
RESP_FILE=$(mktemp)
|
||||
HTTP_CODE=$(curl -sS -o "$RESP_FILE" -w "%{http_code}" -X POST "$API/releases/$RELEASE_ID/assets" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-F "attachment=@$f;filename=$UPLOAD_NAME")
|
||||
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||
echo "✓ Uploaded $UPLOAD_NAME"
|
||||
else
|
||||
echo "✗ Upload failed for $UPLOAD_NAME (HTTP $HTTP_CODE)"
|
||||
python -c 'import pathlib,sys;print(pathlib.Path(sys.argv[1]).read_text(errors="replace")[:2000])' "$RESP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
NAME="linux-arm64-$(basename "$f")"
|
||||
echo "Uploading $NAME..."
|
||||
gh release delete-asset "$TAG" "$NAME" --yes 2>/dev/null || true
|
||||
gh release upload "$TAG" "$f#$NAME"
|
||||
echo "✓ Uploaded $NAME"
|
||||
done
|
||||
|
||||
89
.github/workflows/test.yml
vendored
89
.github/workflows/test.yml
vendored
@ -1,47 +1,53 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 'feature/**'
|
||||
- 'bug/**'
|
||||
- 'fix/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
rust-fmt-check:
|
||||
rust-test:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: rust:1.88-slim
|
||||
image: ghcr.io/msicie/trcaa-linux-amd64:rust1.88-node22
|
||||
credentials:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- run: rustup component add rustfmt
|
||||
- run: cargo fmt --manifest-path src-tauri/Cargo.toml --check
|
||||
|
||||
rust-clippy:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: rust:1.88-slim
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- run: apt-get update -qq && apt-get install -y -qq libwebkit2gtk-4.1-dev libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev patchelf pkg-config perl
|
||||
- run: rustup component add clippy
|
||||
- run: cargo clippy --manifest-path src-tauri/Cargo.toml -- -D warnings
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git/db
|
||||
key: ${{ runner.os }}-cargo-linux-amd64-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-linux-amd64-
|
||||
- name: Install npm dependencies
|
||||
run: npm install --legacy-peer-deps
|
||||
- name: Update version from Git
|
||||
run: node scripts/update-version.mjs
|
||||
- name: Generate lockfile
|
||||
run: cargo generate-lockfile --manifest-path src-tauri/Cargo.toml
|
||||
- name: Rust fmt check
|
||||
run: cargo fmt --manifest-path src-tauri/Cargo.toml --check
|
||||
- name: Rust clippy
|
||||
run: cargo clippy --manifest-path src-tauri/Cargo.toml -- -D warnings
|
||||
- name: Rust tests
|
||||
run: cargo test --manifest-path src-tauri/Cargo.toml -- --test-threads=1
|
||||
|
||||
rust-tests:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: rust:1.88-slim
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- run: apt-get update -qq && apt-get install -y -qq libwebkit2gtk-4.1-dev libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev patchelf pkg-config perl
|
||||
- run: cargo test --manifest-path src-tauri/Cargo.toml
|
||||
|
||||
frontend-typecheck:
|
||||
frontend-test:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node:22-alpine
|
||||
@ -50,17 +56,16 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npx tsc --noEmit
|
||||
|
||||
frontend-tests:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node:22-alpine
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npm run test:run
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-npm-
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
- name: TypeScript type check
|
||||
run: npx tsc --noEmit
|
||||
- name: Run frontend tests
|
||||
run: npm run test:run
|
||||
|
||||
@ -1201,7 +1201,7 @@ Update the `bundle.externalBin` array (currently empty at line 42):
|
||||
|
||||
#### 5.3 Add to CI/CD Pipeline
|
||||
|
||||
**File: `.gitea/workflows/auto-tag.yml`**
|
||||
**File: `.github/workflows/release.yml`**
|
||||
|
||||
Add kubectl download step before build:
|
||||
|
||||
|
||||
15
AGENTS.md
15
AGENTS.md
@ -81,19 +81,20 @@ TypeScript mirrors this shape exactly in `tauriCommands.ts`.
|
||||
|
||||
---
|
||||
|
||||
## CI/CD (Gitea Actions)
|
||||
## CI/CD (GitHub Actions)
|
||||
|
||||
| Workflow | Trigger | Jobs |
|
||||
|----------|---------|------|
|
||||
| `.gitea/workflows/test.yml` | Every push/PR | `rustfmt` → `clippy` → `cargo test` (64 tests) → `tsc --noEmit` → `vitest run` (13 tests) |
|
||||
| `.gitea/workflows/auto-tag.yml` | Push to master | Auto-tag, build linux/amd64 + windows/amd64 + linux/arm64 + macOS, upload assets to Gitea release |
|
||||
| `.github/workflows/test.yml` | Every push/PR targeting `main` | `rust-test` (fmt → clippy → cargo test) · `frontend-test` (tsc → vitest) |
|
||||
| `.github/workflows/release.yml` | Push to `main` (auto-tag), `v*` tags | Auto-tag, build linux/amd64 + linux/arm64 + windows/amd64 + macOS ARM64 + macOS Intel, upload to GitHub Releases |
|
||||
| `.github/workflows/build-images.yml` | `.docker/**` changes on `main` | Build and push pre-baked CI images to `ghcr.io/msicie/` |
|
||||
|
||||
**Artifacts**: `src-tauri/target/{target}/release/bundle/`
|
||||
|
||||
**Environments**:
|
||||
- Test CI images at `172.0.0.29:3000` (pull `trcaa-*:rust1.88-node22`)
|
||||
- Gitea instance: `http://172.0.0.29:3000`
|
||||
- Wiki: sync from `docs/wiki/*.md` → `https://gogs.tftsr.com/sarman/tftsr-devops_investigation/wiki`
|
||||
- Test CI images at `ghcr.io/msicie/` (pull `trcaa-*:rust1.88-node22`)
|
||||
- GitHub repo: `https://github.com/msicie/apollo_nxt-trcaa`
|
||||
- Wiki: sync from `docs/wiki/*.md` → `https://github.com/msicie/apollo_nxt-trcaa/wiki`
|
||||
|
||||
---
|
||||
|
||||
@ -154,4 +155,4 @@ TypeScript mirrors this shape exactly in `tauriCommands.ts`.
|
||||
3. **PII before AI**: Always redact and record hash before external send
|
||||
4. **Port 1420**: Vite dev server is hard-coded to 1420, not 3000
|
||||
5. **Build order**: Rust fmt → clippy → test → TS check → JS test
|
||||
6. **CI images**: Use `172.0.0.29:3000` registry for pre-baked builder images
|
||||
6. **CI images**: Use `ghcr.io/msicie/` registry for pre-baked builder images
|
||||
|
||||
26
CLAUDE.md
26
CLAUDE.md
@ -77,8 +77,9 @@ cargo tauri build # Outputs to src-tauri/target/release/bundle/
|
||||
|
||||
### CI/CD
|
||||
|
||||
- **Test pipeline**: `.woodpecker/test.yml` — runs on every push/PR
|
||||
- **Release pipeline**: `.woodpecker/release.yml` — runs on `v*` tags, produces Linux amd64+arm64 bundles, uploads to Gogs release at `http://172.0.0.29:3000/api/v1`
|
||||
- **Test pipeline**: `.github/workflows/test.yml` — runs on every push/PR targeting `main`
|
||||
- **Release pipeline**: `.github/workflows/release.yml` — runs on every push to `main`, auto-tags, produces multi-platform bundles (Linux amd64+arm64, Windows, macOS arm64+Intel), uploads to GitHub Releases at `https://github.com/msicie/apollo_nxt-trcaa/releases`
|
||||
- **Docker builder images**: `.github/workflows/build-images.yml` — rebuilds `ghcr.io/msicie/trcaa-*` images when `.docker/**` changes on `main`
|
||||
|
||||
---
|
||||
|
||||
@ -162,24 +163,21 @@ On the TypeScript side, `tauriCommands.ts` mirrors this shape exactly.
|
||||
|
||||
Before any text is sent to an AI provider, `apply_redactions` must be called and the resulting SHA-256 hash recorded via `audit::log::write_audit_event`.
|
||||
|
||||
### Woodpecker CI + Gogs Compatibility
|
||||
### GitHub Actions CI
|
||||
|
||||
**Status**: Woodpecker CI v0.15.4 is deployed at `http://172.0.0.29:8084` (direct) and `http://172.0.0.29:8085` (nginx proxy). Webhook delivery from Gogs works, but CI builds are not yet triggering due to hook authentication issues. See `PLAN.md § Phase 11` for full details.
|
||||
All pipelines run on GitHub Actions at `https://github.com/msicie/apollo_nxt-trcaa/actions`.
|
||||
|
||||
Known issues with Woodpecker 0.15.4 + Gogs 0.14:
|
||||
- `token.ParseRequest()` does not read `?token=` URL params (only `Authorization` header and `user_sess` cookie)
|
||||
- The SPA login form uses `login=` field; Gogs backend reads `username=` — a custom login page is served by nginx at `/login` and `/login/form`
|
||||
- Gogs 0.14 has no OAuth2 provider support, blocking upgrade to Woodpecker 2.x
|
||||
|
||||
Gogs token quirk: the `sha1` value returned by `POST /api/v1/users/{user}/tokens` is the **actual bearer token**. The `sha1` and `sha256` columns in the Gogs DB are hashes of that token, not the token itself.
|
||||
- `GITHUB_TOKEN` is the only credential needed — no external secrets required
|
||||
- Builder images are hosted on `ghcr.io/msicie/` (GitHub Container Registry)
|
||||
- Branch protection on `main` requires `rust-test` and `frontend-test` checks to pass, plus Copilot code review, before merging
|
||||
|
||||
---
|
||||
|
||||
## Wiki Maintenance
|
||||
|
||||
The project wiki lives at `https://gogs.tftsr.com/sarman/tftsr-devops_investigation/wiki`.
|
||||
The project wiki lives at `https://github.com/msicie/apollo_nxt-trcaa/wiki`.
|
||||
|
||||
**Source of truth**: `docs/wiki/*.md` in this repo. The `wiki-sync` CI step (in `.woodpecker/test.yml`) automatically pushes any changes to the Gogs wiki on every push to master.
|
||||
**Source of truth**: `docs/wiki/*.md` in this repo. The `wiki-sync` job (in `.github/workflows/release.yml`) automatically pushes any changes to the GitHub wiki on every push to `main`.
|
||||
|
||||
**When making code changes, update the corresponding wiki file in `docs/wiki/` before committing:**
|
||||
|
||||
@ -189,7 +187,7 @@ The project wiki lives at `https://gogs.tftsr.com/sarman/tftsr-devops_investigat
|
||||
| DB schema or migrations (`db/migrations.rs`, `db/models.rs`) | `docs/wiki/Database.md` |
|
||||
| New/changed AI provider (`ai/*.rs`) | `docs/wiki/AI-Providers.md` |
|
||||
| PII patterns or detection logic (`pii/`) | `docs/wiki/PII-Detection.md` |
|
||||
| CI/CD pipeline changes (`.woodpecker/*.yml`) | `docs/wiki/CICD-Pipeline.md` |
|
||||
| CI/CD pipeline changes (`.github/workflows/*.yml`) | `docs/wiki/CICD-Pipeline.md` |
|
||||
| Rust architecture or module layout (`lib.rs`, `state.rs`) | `docs/wiki/Architecture.md` |
|
||||
| Security-relevant changes (capabilities, audit, Stronghold) | `docs/wiki/Security-Model.md` |
|
||||
| Dev setup, prerequisites, build commands | `docs/wiki/Development-Setup.md` |
|
||||
@ -198,7 +196,7 @@ The project wiki lives at `https://gogs.tftsr.com/sarman/tftsr-devops_investigat
|
||||
|
||||
To manually push wiki changes without waiting for CI:
|
||||
```bash
|
||||
cd /tmp/tftsr-wiki # local clone of the wiki git repo
|
||||
cd /tmp/apollo-wiki # local clone of the wiki git repo
|
||||
# edit *.md files, then:
|
||||
git add -A && git commit -m "docs: ..." && git push
|
||||
```
|
||||
|
||||
21
Makefile
21
Makefile
@ -1,10 +1,9 @@
|
||||
GOGS_API := http://172.0.0.29:3000/api/v1
|
||||
GOGS_REPO := sarman/tftsr-devops_investigation
|
||||
GH_REPO := msicie/apollo_nxt-trcaa
|
||||
TAG ?= v0.1.0-alpha
|
||||
TARGET := aarch64-unknown-linux-gnu
|
||||
|
||||
# Build linux/arm64 release artifact natively inside a Docker container,
|
||||
# then upload to the Gogs release for TAG.
|
||||
# then upload to the GitHub release for TAG.
|
||||
.PHONY: release-arm64
|
||||
release-arm64: build-arm64 upload-arm64
|
||||
|
||||
@ -35,15 +34,11 @@ build-arm64:
|
||||
|
||||
.PHONY: upload-arm64
|
||||
upload-arm64:
|
||||
@test -n "$(GOGS_TOKEN)" || (echo "ERROR: set GOGS_TOKEN env var"; exit 1)
|
||||
@RELEASE_ID=$$(curl -sf "$(GOGS_API)/repos/$(GOGS_REPO)/releases/tags/$(TAG)" \
|
||||
-H "Authorization: token $(GOGS_TOKEN)" | \
|
||||
grep -o '"id":[0-9]*' | head -1 | cut -d: -f2); \
|
||||
echo "Release ID: $$RELEASE_ID"; \
|
||||
for f in artifacts/linux-arm64/*; do \
|
||||
@test -n "$(GH_TOKEN)" || (echo "ERROR: set GH_TOKEN env var"; exit 1)
|
||||
@for f in artifacts/linux-arm64/*; do \
|
||||
[ -f "$$f" ] || continue; \
|
||||
echo "Uploading $$f..."; \
|
||||
curl -sf -X POST "$(GOGS_API)/repos/$(GOGS_REPO)/releases/$$RELEASE_ID/assets" \
|
||||
-H "Authorization: token $(GOGS_TOKEN)" \
|
||||
-F "attachment=@$$f;filename=$$(basename $$f)" && echo "OK" || echo "FAIL: $$f"; \
|
||||
NAME="linux-arm64-$$(basename $$f)"; \
|
||||
echo "Uploading $$NAME..."; \
|
||||
GH_TOKEN=$(GH_TOKEN) gh release upload $(TAG) "$$f#$$NAME" \
|
||||
--repo $(GH_REPO) && echo "OK" || echo "FAIL: $$f"; \
|
||||
done
|
||||
|
||||
13
PLAN.md
13
PLAN.md
@ -33,9 +33,11 @@ produces post-mortem documents (Markdown / PDF / DOCX).
|
||||
|
||||
```
|
||||
tftsr/
|
||||
├── .woodpecker/
|
||||
│ ├── test.yml # lint + unit tests on push / PR
|
||||
│ └── release.yml # multi-platform build on tag
|
||||
├── .github/
|
||||
│ └── workflows/
|
||||
│ ├── test.yml # lint + unit tests on push / PR
|
||||
│ ├── release.yml # multi-platform build on tag
|
||||
│ └── build-images.yml # pre-baked CI image builds
|
||||
├── cli/
|
||||
│ ├── package.json
|
||||
│ └── src/
|
||||
@ -285,8 +287,9 @@ All frontend ↔ backend communication goes through Tauri's `invoke()`.
|
||||
|
||||
| Pipeline | Trigger | Steps |
|
||||
|----------|---------|-------|
|
||||
| `.woodpecker/test.yml` | push, PR | `rustfmt` check → Clippy → Rust tests → TS typecheck → Vitest → coverage (main only) |
|
||||
| `.woodpecker/release.yml` | `v*` tag | Build linux-amd64 → Build linux-arm64 → Upload to Gogs release |
|
||||
| `.github/workflows/test.yml` | push, PR | `rustfmt` check → Clippy → Rust tests → TS typecheck → Vitest |
|
||||
| `.github/workflows/release.yml` | push to `main` (auto-tag), `v*` tag | Build linux-amd64 → linux-arm64 → windows-amd64 → macOS ARM64 → macOS Intel → Upload to GitHub Releases |
|
||||
| `.github/workflows/build-images.yml` | `.docker/**` changes on `main` | Build and push pre-baked CI images to `ghcr.io/msicie/` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
50
README.md
50
README.md
@ -4,7 +4,7 @@ A structured, AI-backed desktop tool for IT incident triage, 5-Whys root cause a
|
||||
|
||||
Built with **Tauri 2** (Rust + WebView), **React 18**, **TypeScript**, and **SQLCipher AES-256** encrypted storage.
|
||||
|
||||
**CI status:**  — all checks green (rustfmt · clippy · 64 Rust tests · tsc · vitest)
|
||||
**CI status:**  — all checks green (rustfmt · clippy · 64 Rust tests · tsc · vitest)
|
||||
|
||||
---
|
||||
|
||||
@ -90,8 +90,8 @@ node --version # 22+
|
||||
|
||||
```bash
|
||||
# Clone
|
||||
git clone https://gogs.tftsr.com/sarman/tftsr-devops_investigation.git
|
||||
cd tftsr-devops_investigation
|
||||
git clone https://github.com/msicie/apollo_nxt-trcaa.git
|
||||
cd apollo_nxt-trcaa
|
||||
npm install --legacy-peer-deps
|
||||
|
||||
# Development mode (hot reload)
|
||||
@ -107,14 +107,15 @@ cargo tauri build
|
||||
|
||||
## Releases
|
||||
|
||||
Pre-built installers are attached to each [tagged release](https://gogs.tftsr.com/sarman/tftsr-devops_investigation/releases):
|
||||
Pre-built installers are attached to each [tagged release](https://github.com/msicie/apollo_nxt-trcaa/releases):
|
||||
|
||||
| Platform | Format | Notes |
|
||||
|---|---|---|
|
||||
| Linux amd64 | `.deb`, `.rpm`, `.AppImage` | Standard package or universal AppImage |
|
||||
| Windows amd64 | `.exe` (NSIS), `.msi` | From cross-compile via mingw-w64 |
|
||||
| Linux arm64 | `.deb`, `.rpm`, `.AppImage` | Built natively on arm64 runner |
|
||||
| macOS | — | Requires macOS runner — build locally |
|
||||
| macOS ARM64 | `.dmg` | Native build on `macos-latest` |
|
||||
| macOS Intel | `.dmg` | Native build on `macos-13` |
|
||||
|
||||
---
|
||||
|
||||
@ -173,7 +174,7 @@ To use Claude via AWS Bedrock (ideal for enterprise environments with existing A
|
||||
- API Key: `sk-your-secure-key` (from config)
|
||||
- Model: `bedrock-claude`
|
||||
|
||||
For detailed setup including multiple AWS accounts and Claude Code integration, see the [LiteLLM + Bedrock wiki page](https://gogs.tftsr.com/sarman/tftsr-devops_investigation/wiki/LiteLLM-Bedrock-Setup).
|
||||
For detailed setup including multiple AWS accounts and Claude Code integration, see the [LiteLLM + Bedrock wiki page](https://github.com/msicie/apollo_nxt-trcaa/wiki/LiteLLM-Bedrock-Setup).
|
||||
|
||||
---
|
||||
|
||||
@ -214,11 +215,12 @@ tftsr/
|
||||
├── tests/
|
||||
│ ├── unit/ # Vitest unit tests (PII, session store, settings store)
|
||||
│ └── e2e/ # WebdriverIO + tauri-driver E2E skeletons
|
||||
├── docs/wiki/ # Source of truth for Gitea wiki
|
||||
└── .gitea/
|
||||
├── docs/wiki/ # Source of truth for GitHub wiki
|
||||
└── .github/
|
||||
└── workflows/
|
||||
├── test.yml # CI: rustfmt · clippy · cargo test · tsc · vitest (every push/PR)
|
||||
└── auto-tag.yml # Auto tag + release: linux/amd64 + windows/amd64 + linux/arm64 + macOS
|
||||
├── test.yml # CI: rustfmt · clippy · cargo test · tsc · vitest (every push/PR)
|
||||
├── release.yml # Auto tag + release: linux/amd64 + linux/arm64 + windows/amd64 + macOS ARM64 + macOS Intel
|
||||
└── build-images.yml # Build and push pre-baked CI images to ghcr.io
|
||||
```
|
||||
|
||||
---
|
||||
@ -245,25 +247,27 @@ TAURI_BINARY_PATH=./src-tauri/target/release/tftsr npm run test:e2e
|
||||
|
||||
---
|
||||
|
||||
## CI/CD — Gitea Actions
|
||||
## CI/CD — GitHub Actions
|
||||
|
||||
The project uses **Gitea Actions** (act_runner v0.3.1) connected to the Gitea instance at `gogs.tftsr.com`.
|
||||
The project uses **GitHub Actions** with pre-baked builder images hosted on `ghcr.io/msicie/`.
|
||||
|
||||
| Workflow | Trigger | Jobs |
|
||||
|---|---|---|
|
||||
| `.gitea/workflows/test.yml` | Every push / PR | rustfmt · clippy · cargo test (64) · tsc · vitest (13) |
|
||||
| `.gitea/workflows/auto-tag.yml` | Push to `master` | Auto-tag, then build linux/amd64 + windows/amd64 + linux/arm64 + macOS and upload assets |
|
||||
| `.github/workflows/test.yml` | Every push / PR targeting `main` | `rust-test` (fmt · clippy · cargo test) · `frontend-test` (tsc · vitest) |
|
||||
| `.github/workflows/release.yml` | Push to `main` (auto-tag), then `v*` tags | Auto-tag, build linux/amd64 + linux/arm64 + windows/amd64 + macOS ARM64 + macOS Intel, upload to GitHub Releases |
|
||||
| `.github/workflows/build-images.yml` | Changes to `.docker/**` on `main` | Build and push pre-baked CI images to `ghcr.io/msicie/` |
|
||||
|
||||
**Runners:**
|
||||
**Pre-baked CI images:**
|
||||
|
||||
| Runner | Platform | Host | Purpose |
|
||||
|---|---|---|---|
|
||||
| `amd64-docker-runner` | linux/amd64 | 172.0.0.29 (Docker) | Test pipeline + amd64/windows release builds |
|
||||
| `arm64-native-runner` | linux/arm64 | Local arm64 machine | Native arm64 release builds |
|
||||
| Image | Purpose |
|
||||
|---|---|
|
||||
| `ghcr.io/msicie/trcaa-linux-amd64:rust1.88-node22` | Test pipeline + linux/amd64 + windows cross-compile |
|
||||
| `ghcr.io/msicie/trcaa-linux-arm64:rust1.88-node22` | linux/arm64 release builds |
|
||||
| `ghcr.io/msicie/trcaa-windows-cross:rust1.88-node22` | Windows amd64 cross-compile |
|
||||
|
||||
**Branch protection:** master requires a PR approved by `sarman`, with all 5 CI checks passing before merge.
|
||||
**Branch protection:** `main` requires a PR with `rust-test` + `frontend-test` + CODEOWNER review before merge.
|
||||
|
||||
> See [CI/CD Pipeline wiki](https://gogs.tftsr.com/sarman/tftsr-devops_investigation/wiki/CICD-Pipeline) for full infrastructure docs.
|
||||
> See [CI/CD Pipeline wiki](https://github.com/msicie/apollo_nxt-trcaa/wiki/CICD-Pipeline) for full infrastructure docs.
|
||||
|
||||
---
|
||||
|
||||
@ -321,7 +325,7 @@ Override with the `TFTSR_DATA_DIR` environment variable.
|
||||
| 8 | RCA & Post-Mortem Generation | ✅ Complete |
|
||||
| 9 | History & Search | 🔲 Pending |
|
||||
| 10 | Integrations (Confluence, ServiceNow, ADO) | 🔲 v0.2 |
|
||||
| 11 | CI/CD Pipeline | ✅ Complete — Gitea Actions, all checks green |
|
||||
| 12 | Release Packaging | ✅ linux/amd64 · linux/arm64 (native) · windows/amd64 |
|
||||
| 11 | CI/CD Pipeline | ✅ Complete — GitHub Actions, all checks green |
|
||||
| 12 | Release Packaging | ✅ linux/amd64 · linux/arm64 · windows/amd64 · macOS ARM64 · macOS Intel |
|
||||
|
||||
---
|
||||
|
||||
@ -51,16 +51,9 @@ These expose internal service infrastructure to anyone reading the source and in
|
||||
|
||||
---
|
||||
|
||||
### C3. Private Gogs Server IP Exposed in All CI Workflows
|
||||
### C3. Private Gogs Server IP in CI Workflows — RESOLVED
|
||||
|
||||
**Files**:
|
||||
- `.gitea/workflows/test.yml` (lines 17, 44, 72, 99, 126)
|
||||
- `.gitea/workflows/auto-tag.yml` (lines 31, 52, 79, 95, 97, 141, 162, 227, 252, 313, 338, 401, 464)
|
||||
- `.gitea/workflows/build-images.yml` (lines 4, 10, 11, 16-18, 33, 46, 69, 92)
|
||||
|
||||
**Issue**: All CI workflow files reference `172.0.0.29:3000` (a private Gogs instance) and `sarman` username. While the IP is RFC1918 private address space, it reveals internal infrastructure topology and the developer's username across dozens of lines. The `build-images.yml` also exposes `REGISTRY_USER: sarman` and container registry details.
|
||||
|
||||
**Recommended Fix**: Before open-sourcing, replace all workflow files with GitHub Actions equivalents, or at minimum replace the hardcoded private IP and username with parameterized variables or remove the `.gitea/` directory entirely if moving to GitHub.
|
||||
**Status**: Resolved. All `.gitea/workflows/` files have been removed. CI/CD has been migrated to GitHub Actions (`.github/workflows/`). Container images are now hosted on `ghcr.io/msicie/` (public). No private IPs or internal infrastructure references remain in CI configuration.
|
||||
|
||||
---
|
||||
|
||||
@ -263,13 +256,9 @@ style-src 'self' 'unsafe-inline'
|
||||
|
||||
---
|
||||
|
||||
### L4. Username `sarman` Embedded in CI Workflows and Makefile
|
||||
### L4. Username in CI Workflows and Makefile — RESOLVED
|
||||
|
||||
**Files**: `.gitea/workflows/*.yml`, `Makefile` line 2
|
||||
|
||||
**Issue**: The developer's username appears throughout CI configuration. While not a security vulnerability per se, it is a privacy concern for open-source release.
|
||||
|
||||
**Recommended Fix**: Parameterize the username in CI workflows. Update the Makefile to use a generic repository reference.
|
||||
**Status**: Resolved. `.gitea/workflows/` removed. GitHub Actions workflows use `${{ github.actor }}` and `ghcr.io/msicie/` (org-scoped). Makefile updated to reference `msicie/apollo_nxt-trcaa` via `gh` CLI.
|
||||
|
||||
---
|
||||
|
||||
@ -317,7 +306,7 @@ The following practices are already well-implemented:
|
||||
|----------|--------|--------|
|
||||
| **P0** | Remove `GenAI API User Guide.md` and `HANDOFF-MSI-GENAI.md` from repo and git history | Small |
|
||||
| **P0** | Remove `commandcentral.com` URLs from CSP and hardcoded MSI headers from `openai.rs` | Small |
|
||||
| **P0** | Replace or parameterize private IP (`172.0.0.29`) and username in all `.gitea/` workflows | Medium |
|
||||
| **P0** | ~~Replace or parameterize private IP (`172.0.0.29`) and username in all `.gitea/` workflows~~ **RESOLVED** — migrated to GitHub Actions | — |
|
||||
| **P1** | Replace hardcoded dev encryption keys with auto-generated per-install keys | Small |
|
||||
| **P1** | Use proper KDF (PBKDF2/HKDF) for AES key derivation in `auth.rs` | Small |
|
||||
| **P1** | Auto-generate encryption key for credential storage (mirror `connection.rs` pattern) | Small |
|
||||
|
||||
@ -685,38 +685,39 @@ graph LR
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Source Control"
|
||||
GOGS[Gogs / Gitea\ngogs.tftsr.com\nSarman Repository]
|
||||
GITHUB[GitHub\ngithub.com/msicie/apollo_nxt-trcaa\nmain branch]
|
||||
end
|
||||
|
||||
subgraph "CI/CD Triggers"
|
||||
PR_TRIGGER[PR Opened/Updated\ntest.yml workflow]
|
||||
MASTER_TRIGGER[Push to master\nauto-tag.yml workflow]
|
||||
MAIN_TRIGGER[Push to main\nrelease.yml workflow]
|
||||
DOCKER_TRIGGER[.docker/ changes\nbuild-images.yml workflow]
|
||||
end
|
||||
|
||||
subgraph "Test Runner — amd64-docker-runner"
|
||||
subgraph "Test Runner — ubuntu-latest"
|
||||
RUSTFMT[1. rustfmt\nFormat Check]
|
||||
CLIPPY[2. clippy\n-D warnings]
|
||||
CARGO_TEST[3. cargo test\n64 Rust tests]
|
||||
TSC[4. tsc --noEmit\nType Check]
|
||||
VITEST[5. vitest run\n13 JS tests]
|
||||
VITEST[5. vitest run\nJS tests]
|
||||
end
|
||||
|
||||
subgraph "Release Builders (Parallel)"
|
||||
AMD64[linux/amd64\nDocker: trcaa-linux-amd64\n.deb .rpm .AppImage]
|
||||
WINDOWS[windows/amd64\nDocker: trcaa-windows-cross\n.exe .msi]
|
||||
ARM64[linux/arm64\narm64 native runner\n.deb .rpm .AppImage]
|
||||
MACOS[macOS arm64\nnative macOS runner\n.app .dmg]
|
||||
AMD64[linux/amd64\nghcr.io/msicie/trcaa-linux-amd64\n.deb .rpm .AppImage]
|
||||
WINDOWS[windows/amd64\nghcr.io/msicie/trcaa-windows-cross\n.exe .msi]
|
||||
ARM64[linux/arm64\nghcr.io/msicie/trcaa-linux-arm64\n.deb .rpm .AppImage]
|
||||
MACOS_ARM[macOS ARM64\nmacos-latest runner\n.dmg]
|
||||
MACOS_INTEL[macOS Intel\nmacos-13 runner\n.dmg]
|
||||
end
|
||||
|
||||
subgraph "Artifact Storage"
|
||||
RELEASE[Gitea Release\nv0.x.x tags\nAll platform assets]
|
||||
REGISTRY[Gitea Container Registry\n172.0.0.29:3000\nCI Docker images]
|
||||
RELEASE[GitHub Releases\nv0.x.x tags\nAll platform assets]
|
||||
REGISTRY[ghcr.io/msicie/\nCI Docker images]
|
||||
end
|
||||
|
||||
GOGS --> PR_TRIGGER
|
||||
GOGS --> MASTER_TRIGGER
|
||||
GOGS --> DOCKER_TRIGGER
|
||||
GITHUB --> PR_TRIGGER
|
||||
GITHUB --> MAIN_TRIGGER
|
||||
GITHUB --> DOCKER_TRIGGER
|
||||
|
||||
PR_TRIGGER --> RUSTFMT
|
||||
RUSTFMT --> CLIPPY
|
||||
@ -724,15 +725,17 @@ graph TB
|
||||
CARGO_TEST --> TSC
|
||||
TSC --> VITEST
|
||||
|
||||
MASTER_TRIGGER --> AMD64
|
||||
MASTER_TRIGGER --> WINDOWS
|
||||
MASTER_TRIGGER --> ARM64
|
||||
MASTER_TRIGGER --> MACOS
|
||||
MAIN_TRIGGER --> AMD64
|
||||
MAIN_TRIGGER --> WINDOWS
|
||||
MAIN_TRIGGER --> ARM64
|
||||
MAIN_TRIGGER --> MACOS_ARM
|
||||
MAIN_TRIGGER --> MACOS_INTEL
|
||||
|
||||
AMD64 --> RELEASE
|
||||
WINDOWS --> RELEASE
|
||||
ARM64 --> RELEASE
|
||||
MACOS --> RELEASE
|
||||
MACOS_ARM --> RELEASE
|
||||
MACOS_INTEL --> RELEASE
|
||||
|
||||
DOCKER_TRIGGER --> REGISTRY
|
||||
|
||||
|
||||
@ -4,67 +4,39 @@
|
||||
|
||||
| Component | URL | Notes |
|
||||
|-----------|-----|-------|
|
||||
| Gitea | `https://gogs.tftsr.com` / `http://172.0.0.29:3000` | Git server (migrated from Gogs 0.14) |
|
||||
| Woodpecker CI (direct) | `http://172.0.0.29:8084` | v2.x |
|
||||
| Woodpecker CI (proxy) | `http://172.0.0.29:8085` | nginx reverse proxy |
|
||||
| PostgreSQL (Gitea DB) | Container: `gogs_postgres_db` | DB: `gogsdb`, User: `gogs` |
|
||||
|
||||
### CI Agents
|
||||
|
||||
| Agent | Platform | Host | Purpose |
|
||||
|-------|----------|------|---------|
|
||||
| `gitea_act_runner_amd64` (Docker) | `linux-amd64` | 172.0.0.29 | Native x86_64 — test builds + amd64/windows release |
|
||||
| `act_runner` (systemd) | `linux-arm64` | 172.0.0.29 | Native aarch64 — arm64 release builds |
|
||||
| `act_runner` (launchd) | `macos-arm64` | sarman's local Mac | Native Apple Silicon — macOS `.dmg` release builds |
|
||||
|
||||
Agent labels configured in `~/.config/act_runner/config.yaml`:
|
||||
```yaml
|
||||
runner:
|
||||
labels:
|
||||
- "macos-arm64:host"
|
||||
```
|
||||
macOS runner runs jobs **directly on the host** (no Docker container) — macOS SDK cannot run in Docker.
|
||||
| GitHub | `https://github.com/msicie/apollo_nxt-trcaa` | Git server and CI/CD platform |
|
||||
| GitHub Actions | Native to GitHub | Hosted runners (`ubuntu-latest`, `macos-latest`, `macos-13`) |
|
||||
| ghcr.io | `ghcr.io/msicie/` | GitHub Container Registry for pre-baked CI images |
|
||||
|
||||
---
|
||||
|
||||
## Pre-baked Builder Images
|
||||
|
||||
CI build and test jobs use pre-baked Docker images pushed to the local Gitea registry
|
||||
at `172.0.0.29:3000`. These images bake in all system dependencies (Tauri libs, Node.js,
|
||||
Rust toolchain, cross-compilers) so that CI jobs skip package installation entirely.
|
||||
CI build and test jobs use pre-baked Docker images pushed to `ghcr.io/msicie/`. These images bake in all system dependencies (Tauri libs, Node.js, Rust toolchain, cross-compilers) so that CI jobs skip package installation entirely.
|
||||
|
||||
| Image | Used by jobs | Contents |
|
||||
|-------|-------------|----------|
|
||||
| `172.0.0.29:3000/sarman/trcaa-linux-amd64:rust1.88-node22` | `rust-fmt-check`, `rust-clippy`, `rust-tests`, `build-linux-amd64` | Rust 1.88 + rustfmt + clippy + Tauri amd64 libs + Node.js 22 |
|
||||
| `172.0.0.29:3000/sarman/trcaa-windows-cross:rust1.88-node22` | `build-windows-amd64` | Rust 1.88 + mingw-w64 + NSIS + Node.js 22 |
|
||||
| `172.0.0.29:3000/sarman/trcaa-linux-arm64:rust1.88-node22` | `build-linux-arm64` | Rust 1.88 + aarch64 cross-toolchain + arm64 multiarch libs + Node.js 22 |
|
||||
| `ghcr.io/msicie/trcaa-linux-amd64:rust1.88-node22` | `rust-test`, `build-linux-amd64` | Rust 1.88 + rustfmt + clippy + Tauri amd64 libs + Node.js 22 |
|
||||
| `ghcr.io/msicie/trcaa-windows-cross:rust1.88-node22` | `build-windows-amd64` | Rust 1.88 + mingw-w64 + NSIS + Node.js 22 |
|
||||
| `ghcr.io/msicie/trcaa-linux-arm64:rust1.88-node22` | `build-linux-arm64` | Rust 1.88 + aarch64 cross-toolchain + arm64 multiarch libs + Node.js 22 |
|
||||
|
||||
**Rebuild triggers:** Rust toolchain version bump, webkit2gtk/gtk major version change, Node.js major version change.
|
||||
|
||||
**How to rebuild images:**
|
||||
1. Trigger `build-images.yml` via `workflow_dispatch` in the Gitea Actions UI
|
||||
2. Confirm all 3 images appear in the Gitea package/container registry at `172.0.0.29:3000`
|
||||
1. Trigger `build-images.yml` via Actions → Build CI Docker Images → Run workflow
|
||||
2. Confirm all 3 images appear in `ghcr.io/msicie/`
|
||||
3. Only then merge workflow changes that depend on the new image contents
|
||||
|
||||
**Server prerequisite — insecure registry** (one-time, on 172.0.0.29):
|
||||
```sh
|
||||
echo '{"insecure-registries":["172.0.0.29:3000"]}' | sudo tee /etc/docker/daemon.json
|
||||
sudo systemctl restart docker
|
||||
```
|
||||
This must be configured on every machine running an act_runner for the runner's Docker
|
||||
daemon to pull from the local HTTP registry.
|
||||
|
||||
---
|
||||
|
||||
## Cargo and npm Caching
|
||||
|
||||
All Rust and build jobs use `actions/cache@v3` to cache downloaded package artifacts.
|
||||
Gitea 1.22 implements the GitHub Actions cache API natively.
|
||||
All Rust and build jobs use `actions/cache@v4` to cache downloaded package artifacts.
|
||||
|
||||
**Cargo cache** (Rust jobs):
|
||||
```yaml
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
@ -78,7 +50,7 @@ Gitea 1.22 implements the GitHub Actions cache API natively.
|
||||
**npm cache** (frontend and build jobs):
|
||||
```yaml
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
@ -92,168 +64,108 @@ Cache keys for cross-compile jobs use a suffix to avoid collisions:
|
||||
|
||||
---
|
||||
|
||||
## Test Pipeline (`.gitea/workflows/test.yml`)
|
||||
## Test Pipeline (`.github/workflows/test.yml`)
|
||||
|
||||
**Triggers:** Pull requests only.
|
||||
**Triggers:** Every push to `main`, `feature/**`, `bug/**`, `fix/**` branches; pull requests targeting `main`.
|
||||
|
||||
```
|
||||
Pipeline jobs (run in parallel):
|
||||
1. rust-fmt-check → cargo fmt --check
|
||||
2. rust-clippy → cargo clippy -- -D warnings
|
||||
3. rust-tests → cargo test (64 tests)
|
||||
4. frontend-typecheck → npx tsc --noEmit
|
||||
5. frontend-tests → npm run test:run (13 Vitest tests)
|
||||
Jobs (run in parallel):
|
||||
rust-test → cargo fmt --check
|
||||
→ cargo clippy -- -D warnings
|
||||
→ cargo test (64 tests)
|
||||
frontend-test → npx tsc --noEmit
|
||||
→ npm run test:run (Vitest tests)
|
||||
```
|
||||
|
||||
**Docker images used:**
|
||||
- `172.0.0.29:3000/sarman/trcaa-linux-amd64:rust1.88-node22` — Rust steps (replaces `rust:1.88-slim`)
|
||||
- `node:22-alpine` — Frontend steps
|
||||
**Docker image used:**
|
||||
- `ghcr.io/msicie/trcaa-linux-amd64:rust1.88-node22` — both Rust and frontend steps
|
||||
|
||||
Job names `rust-test` and `frontend-test` must match exactly — these are the required status check names in branch protection on `main`.
|
||||
|
||||
---
|
||||
|
||||
## Release Pipeline (`.gitea/workflows/auto-tag.yml`)
|
||||
## Release Pipeline (`.github/workflows/release.yml`)
|
||||
|
||||
**Triggers:** Pushes to `master` (auto-tag), then release build/upload jobs run after `autotag`.
|
||||
**Triggers:** Push to `main` (auto-tag job), then release build/upload jobs run after `autotag` completes on `v*` tags.
|
||||
|
||||
Auto tags are created by `.gitea/workflows/auto-tag.yml` using `git tag` + `git push`.
|
||||
Release jobs are executed in the same workflow and depend on `autotag` completion.
|
||||
Auto-tags are created by the `autotag` job using `git tag` + `git push`. Release jobs depend on `autotag` and run in parallel.
|
||||
|
||||
```
|
||||
Jobs (run in parallel after autotag):
|
||||
build-linux-amd64 → image: trcaa-linux-amd64:rust1.88-node22
|
||||
→ cargo tauri build (x86_64-unknown-linux-gnu)
|
||||
→ {.deb, .rpm, .AppImage} uploaded to Gitea release
|
||||
→ fails fast if no Linux artifacts are produced
|
||||
build-windows-amd64 → image: trcaa-windows-cross:rust1.88-node22
|
||||
→ cargo tauri build (x86_64-pc-windows-gnu) via mingw-w64
|
||||
→ {.exe, .msi} uploaded to Gitea release
|
||||
→ fails fast if no Windows artifacts are produced
|
||||
build-linux-arm64 → image: trcaa-linux-arm64:rust1.88-node22 (ubuntu:22.04-based)
|
||||
→ cargo tauri build (aarch64-unknown-linux-gnu)
|
||||
→ {.deb, .rpm, .AppImage} uploaded to Gitea release
|
||||
→ fails fast if no Linux artifacts are produced
|
||||
build-macos-arm64 → cargo tauri build (aarch64-apple-darwin) — runs on local Mac
|
||||
→ {.dmg} uploaded to Gitea release
|
||||
→ existing same-name assets are deleted before upload (rerun-safe)
|
||||
→ unsigned; after install run: xattr -cr /Applications/TFTSR.app
|
||||
```
|
||||
build-linux-amd64 → image: ghcr.io/msicie/trcaa-linux-amd64:rust1.88-node22
|
||||
→ cargo tauri build (x86_64-unknown-linux-gnu)
|
||||
→ {.deb, .rpm, .AppImage} uploaded to GitHub Release
|
||||
→ fails fast if no Linux artifacts are produced
|
||||
|
||||
**Per-step agent routing (Woodpecker 2.x labels):**
|
||||
build-windows-amd64 → image: ghcr.io/msicie/trcaa-windows-cross:rust1.88-node22
|
||||
→ cargo tauri build (x86_64-pc-windows-gnu) via mingw-w64
|
||||
→ {.exe, .msi} uploaded to GitHub Release
|
||||
→ fails fast if no Windows artifacts are produced
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: build-linux-amd64
|
||||
labels:
|
||||
platform: linux/amd64 # → woodpecker_agent on 172.0.0.29
|
||||
build-linux-arm64 → image: ghcr.io/msicie/trcaa-linux-arm64:rust1.88-node22
|
||||
→ cargo tauri build (aarch64-unknown-linux-gnu)
|
||||
→ {.deb, .rpm, .AppImage} uploaded to GitHub Release
|
||||
→ fails fast if no Linux artifacts are produced
|
||||
|
||||
- name: build-linux-arm64
|
||||
labels:
|
||||
platform: linux/arm64 # → woodpecker-agent.service on local arm64 machine
|
||||
```
|
||||
build-macos-arm64 → runs-on: macos-latest (Apple Silicon)
|
||||
→ cargo tauri build (aarch64-apple-darwin) natively
|
||||
→ {.dmg} uploaded to GitHub Release
|
||||
→ existing same-name assets are deleted before upload (rerun-safe)
|
||||
→ unsigned; after install run: xattr -cr /Applications/TRCAA.app
|
||||
|
||||
**Multi-agent workspace isolation:**
|
||||
|
||||
Steps routed to different agents do **not** share a workspace. The arm64 step clones
|
||||
the repo directly within its commands (using `http://172.0.0.29:3000`, accessible from
|
||||
the local machine) and uploads its artifacts inline. The `upload-release` step (amd64)
|
||||
handles amd64 + windows artifacts only.
|
||||
|
||||
**Clone override (auto-tag.yml — amd64 workspace):**
|
||||
|
||||
```yaml
|
||||
clone:
|
||||
git:
|
||||
image: alpine/git
|
||||
network_mode: gogs_default
|
||||
commands:
|
||||
- git init -b master
|
||||
- git remote add origin http://gitea_app:3000/sarman/tftsr-devops_investigation.git
|
||||
- git fetch --depth=1 origin +refs/tags/${CI_COMMIT_TAG}:refs/tags/${CI_COMMIT_TAG}
|
||||
- git checkout ${CI_COMMIT_TAG}
|
||||
build-macos-intel → runs-on: macos-13 (Intel x86_64)
|
||||
→ cargo tauri build (x86_64-apple-darwin) natively
|
||||
→ {.dmg} uploaded to GitHub Release
|
||||
```
|
||||
|
||||
**Windows cross-compile environment:**
|
||||
```yaml
|
||||
environment:
|
||||
env:
|
||||
TARGET: x86_64-pc-windows-gnu
|
||||
CC_x86_64_pc_windows_gnu: x86_64-w64-mingw32-gcc
|
||||
CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER: x86_64-w64-mingw32-gcc
|
||||
OPENSSL_NO_VENDOR: "0"
|
||||
OPENSSL_STATIC: "1"
|
||||
```
|
||||
|
||||
**Artifacts per platform:**
|
||||
- Linux amd64: `.deb`, `.rpm`, `.AppImage`
|
||||
- Windows amd64: `.exe` (NSIS installer), `.msi`
|
||||
- Linux arm64: `.deb`, `.rpm`, `.AppImage`
|
||||
|
||||
**Upload step (requires gogs_default network for amd64, host IP for arm64):**
|
||||
```yaml
|
||||
# amd64 upload step
|
||||
upload-release:
|
||||
image: curlimages/curl:latest
|
||||
labels:
|
||||
platform: linux/amd64
|
||||
network_mode: gogs_default
|
||||
secrets: [GOGS_TOKEN]
|
||||
```
|
||||
|
||||
The `GOGS_TOKEN` Woodpecker secret must be created via the Woodpecker UI or API after
|
||||
migration. The secret name stays `GOGS_TOKEN` for pipeline compatibility.
|
||||
|
||||
**Gitea Release API (replaces Gogs API — same endpoints, different container name):**
|
||||
```bash
|
||||
# Create release
|
||||
POST http://gitea_app:3000/api/v1/repos/sarman/tftsr-devops_investigation/releases
|
||||
Authorization: token $GOGS_TOKEN
|
||||
|
||||
# Upload artifact
|
||||
POST http://gitea_app:3000/api/v1/repos/sarman/tftsr-devops_investigation/releases/{id}/assets
|
||||
```
|
||||
|
||||
From the arm64 agent (local machine), use `http://172.0.0.29:3000/api/v1` instead.
|
||||
- macOS ARM64: `.dmg`
|
||||
- macOS Intel: `.dmg`
|
||||
|
||||
---
|
||||
|
||||
## Multi-File Pipeline Support (Woodpecker 2.x)
|
||||
## Build Images Workflow (`.github/workflows/build-images.yml`)
|
||||
|
||||
Woodpecker 2.x supports multiple pipeline files in the `.woodpecker/` directory.
|
||||
All `.yml` files are evaluated on every trigger; `when:` conditions control which
|
||||
pipelines actually run.
|
||||
**Triggers:** Changes to `.docker/**` on `main`; manual `workflow_dispatch`.
|
||||
|
||||
Current files:
|
||||
- `.woodpecker/test.yml` — runs on every push/PR
|
||||
- `.woodpecker/release.yml` — runs on `v*` tags only
|
||||
Builds and pushes all three pre-baked CI images to `ghcr.io/msicie/`:
|
||||
```
|
||||
Jobs (all run on ubuntu-latest):
|
||||
build-linux-amd64-image → .docker/Dockerfile.linux-amd64
|
||||
→ ghcr.io/msicie/trcaa-linux-amd64:rust1.88-node22
|
||||
|
||||
No DB config path switching needed (unlike Woodpecker 0.15.4).
|
||||
build-windows-cross-image → .docker/Dockerfile.windows-cross
|
||||
→ ghcr.io/msicie/trcaa-windows-cross:rust1.88-node22
|
||||
|
||||
---
|
||||
build-linux-arm64-image → .docker/Dockerfile.linux-arm64
|
||||
→ ghcr.io/msicie/trcaa-linux-arm64:rust1.88-node22
|
||||
```
|
||||
|
||||
## Webhook Configuration
|
||||
|
||||
**Woodpecker 2.x with Gitea OAuth2:**
|
||||
|
||||
After migration, Woodpecker 2.x registers webhooks automatically when a repo is
|
||||
activated via the UI. No manual JWT-signed webhook setup required.
|
||||
|
||||
1. Log in at `http://172.0.0.29:8085` via Gitea OAuth2
|
||||
2. Add repo `sarman/tftsr-devops_investigation`
|
||||
3. Woodpecker creates webhook in Gitea automatically
|
||||
Authentication: `docker login ghcr.io -u ${{ github.actor }} --password-stdin <<< "${{ secrets.GITHUB_TOKEN }}"`. Requires `packages: write` permission.
|
||||
|
||||
---
|
||||
|
||||
## Branch Protection
|
||||
|
||||
Master branch is protected: all changes require a PR.
|
||||
|
||||
```sql
|
||||
-- Gitea branch protection (via psql on gogs_postgres_db container)
|
||||
-- Check protection
|
||||
SELECT name, protected, require_pull_request FROM protect_branch WHERE repo_id=42;
|
||||
|
||||
-- Temporarily disable for urgent fixes (restore immediately after!)
|
||||
UPDATE protect_branch SET protected=false WHERE repo_id=42 AND name='master';
|
||||
-- ... push ...
|
||||
UPDATE protect_branch SET protected=true, require_pull_request=true WHERE repo_id=42 AND name='master';
|
||||
```
|
||||
`main` branch requires:
|
||||
- All changes via PR
|
||||
- 1 approving review
|
||||
- CODEOWNER review (`@Shaun-Arman-VFK387_moto` and `@github-copilot`)
|
||||
- Required status checks: `rust-test`, `frontend-test`
|
||||
- `enforce_admins: false` — owner and admins can bypass with `gh pr merge --admin`
|
||||
|
||||
---
|
||||
|
||||
@ -264,7 +176,7 @@ Configuration lives in `cliff.toml` at the repo root.
|
||||
|
||||
### How it works
|
||||
|
||||
A `changelog` job in `auto-tag.yml` runs in parallel with the build jobs, immediately
|
||||
A `changelog` job in `release.yml` runs in parallel with the build jobs, immediately
|
||||
after `autotag` completes:
|
||||
|
||||
1. Clones the full repo history with all tags (`--depth=2147483647` — git-cliff needs
|
||||
@ -272,9 +184,9 @@ after `autotag` completes:
|
||||
2. Downloads the git-cliff v2.7.0 static musl binary (~5 MB, no image change needed).
|
||||
3. Runs `git-cliff --output CHANGELOG.md` to regenerate the full cumulative changelog.
|
||||
4. Runs `git-cliff --latest --strip all` to produce release notes for the new tag only.
|
||||
5. PATCHes the Gitea release body with those notes (replaces the static `"Release vX.Y.Z"`).
|
||||
6. Commits `CHANGELOG.md` to master with `[skip ci]` appended to the message.
|
||||
The `[skip ci]` token prevents `auto-tag.yml` from re-triggering on the CHANGELOG commit.
|
||||
5. Updates the GitHub Release body with those notes via `gh release edit`.
|
||||
6. Commits `CHANGELOG.md` to `main` with `[skip ci]` appended to the message.
|
||||
The `[skip ci]` token prevents `release.yml` from re-triggering on the CHANGELOG commit.
|
||||
7. Uploads `CHANGELOG.md` as a release asset (replaces any previous version).
|
||||
|
||||
### cliff.toml reference
|
||||
@ -289,17 +201,9 @@ after `autotag` completes:
|
||||
|
||||
### Loop prevention
|
||||
|
||||
The `[skip ci]` suffix on the CHANGELOG commit message is recognised by Gitea Actions
|
||||
The `[skip ci]` suffix on the CHANGELOG commit message is recognised by GitHub Actions
|
||||
and causes the workflow to be skipped for that push. Without it, the CHANGELOG commit
|
||||
would trigger `auto-tag.yml` again, incrementing the patch version forever.
|
||||
|
||||
### Bootstrap
|
||||
|
||||
The initial `CHANGELOG.md` was generated locally before the first PR:
|
||||
```sh
|
||||
git-cliff --config cliff.toml --output CHANGELOG.md
|
||||
```
|
||||
Subsequent runs are fully automated by CI.
|
||||
would trigger `release.yml` again, incrementing the patch version forever.
|
||||
|
||||
---
|
||||
|
||||
@ -312,27 +216,16 @@ index is duplicated and certain `-dev` package pairs cannot be co-installed beca
|
||||
don't declare `Multi-Arch: same`. This produces `E: Unable to correct problems, you have
|
||||
held broken packages` and cannot be fixed by tweaking `sources.list` entries.
|
||||
|
||||
**Fix**: Use `ubuntu:22.04` as the container image. Ubuntu routes arm64 through
|
||||
`ports.ubuntu.com/ubuntu-ports` — a separate mirror from `archive.ubuntu.com` (amd64).
|
||||
There are no cross-arch index overlaps and the dependency resolver succeeds. Rust must be
|
||||
installed manually via `rustup` since it is not pre-installed in the Ubuntu base image.
|
||||
**Fix**: Use `ubuntu:22.04` as the container image (see `Dockerfile.linux-arm64`). Ubuntu
|
||||
routes arm64 through `ports.ubuntu.com/ubuntu-ports` — a separate mirror from
|
||||
`archive.ubuntu.com` (amd64). There are no cross-arch index overlaps and the dependency
|
||||
resolver succeeds. Rust must be installed manually via `rustup`.
|
||||
|
||||
### Step Containers Cannot Reach `gitea_app`
|
||||
Default Docker bridge containers cannot resolve `gitea_app` or reach `172.0.0.29:3000`
|
||||
(host firewall). Fix: use `network_mode: gogs_default` in any step that needs Gitea
|
||||
access. Requires `repo_trusted=1`.
|
||||
|
||||
### `CI=woodpecker` Rejected by Tauri CLI
|
||||
Woodpecker sets `CI=woodpecker`; `cargo tauri build` expects a boolean. Fix: prefix with
|
||||
`CI=true cargo tauri build`.
|
||||
|
||||
### Agent Stalls After Server Restart
|
||||
After restarting the Woodpecker server, the agent may enter a loop cleaning up orphaned
|
||||
containers and stop picking up new builds. Fix:
|
||||
```bash
|
||||
docker rm -f $(docker ps -aq --filter 'name=0_')
|
||||
docker volume rm $(docker volume ls -q | grep '0_')
|
||||
docker restart woodpecker_agent
|
||||
### `CI=true` Required by Tauri CLI
|
||||
GitHub Actions sets `CI=true` by default — `cargo tauri build` accepts this without
|
||||
modification. Prefix the command explicitly if your runner environment is unusual:
|
||||
```yaml
|
||||
- run: CI=true cargo tauri build --target $TARGET
|
||||
```
|
||||
|
||||
### Windows DLL Export Ordinal Too Large
|
||||
@ -344,32 +237,17 @@ Fix: `src-tauri/.cargo/config.toml`:
|
||||
rustflags = ["-C", "link-arg=-Wl,--exclude-all-symbols"]
|
||||
```
|
||||
|
||||
### GOGS_TOKEN Secret Must Be Recreated After Migration
|
||||
After migrating from Woodpecker 0.15.4 to 2.x, recreate the `GOGS_TOKEN` secret:
|
||||
1. Log in to Gitea, create a new API token under Settings → Applications
|
||||
2. In Woodpecker UI → Repository → Secrets, add secret `GOGS_TOKEN` with the token value
|
||||
### Release Artifacts Not Uploaded
|
||||
|
||||
---
|
||||
**Cause:** `GITHUB_TOKEN` needs `contents: write` permission in the workflow.
|
||||
|
||||
## Gitea PostgreSQL Access
|
||||
|
||||
```bash
|
||||
docker exec gogs_postgres_db psql -U gogs -d gogsdb -c "SELECT id, lower_name FROM repository;"
|
||||
Verify the `release.yml` permissions block includes:
|
||||
```yaml
|
||||
permissions:
|
||||
contents: write
|
||||
```
|
||||
|
||||
> Database name is `gogsdb` (unchanged from Gogs migration).
|
||||
|
||||
---
|
||||
|
||||
## Migration Notes (Gogs 0.14 → Gitea)
|
||||
|
||||
Gitea auto-migrates the Gogs PostgreSQL schema on first start. Users, repos, teams, and
|
||||
issues are preserved. API tokens stored in the DB are also migrated but should be
|
||||
regenerated for security.
|
||||
|
||||
Key changes after migration:
|
||||
- Container name: `gogs_app` → `gitea_app`
|
||||
- Config dir: `/data/gitea` (was `/data/gogs` inside container, same host volume)
|
||||
- Repo dir: `gogs-repositories` → `gitea-repositories` (renamed on host during migration)
|
||||
- OAuth2 provider: Gitea now supports OAuth2 (Woodpecker 2.x uses this for login)
|
||||
- Woodpecker 2.x multi-file pipeline support enabled (no more single config file limitation)
|
||||
### Runner Group Restrictions (Enterprise)
|
||||
GitHub Enterprise organizations may restrict which repositories can use GitHub-hosted
|
||||
runners. If jobs are stuck in queue, contact your org admin to add the repository to the
|
||||
allowed runner group, or temporarily make the repo public for the bootstrap run.
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
**Troubleshooting and RCA Assistant** is a secure desktop application for guided IT incident triage, root cause analysis (RCA), and post-mortem documentation. Built with Tauri 2.x (Rust + WebView) and React 18.
|
||||
|
||||
**CI:**  — rustfmt · clippy · 64 Rust tests · tsc · vitest — all green
|
||||
**CI:**  — rustfmt · clippy · 64 Rust tests · tsc · vitest — all green
|
||||
|
||||
## Quick Navigation
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
| [LiteLLM + Bedrock Setup](wiki/LiteLLM-Bedrock-Setup) | AWS Bedrock integration via LiteLLM proxy |
|
||||
| [PII Detection](wiki/PII-Detection) | Patterns, redaction flow, security |
|
||||
| [IPC Commands](wiki/IPC-Commands) | Full list of Tauri backend commands |
|
||||
| [CI/CD Pipeline](wiki/CICD-Pipeline) | Gitea Actions setup, multi-platform builds, act_runner config |
|
||||
| [CI/CD Pipeline](wiki/CICD-Pipeline) | GitHub Actions setup, multi-platform builds, pre-baked ghcr.io images |
|
||||
| [Security Model](wiki/Security-Model) | Encryption, audit trail, capabilities |
|
||||
| [Integrations](wiki/Integrations) | Confluence, ServiceNow, Azure DevOps (v0.2) |
|
||||
| [Troubleshooting](wiki/Troubleshooting) | Known issues and fixes |
|
||||
@ -45,7 +45,7 @@
|
||||
|
||||
**Platforms:** linux/amd64 · linux/arm64 · windows/amd64 (.deb, .rpm, .AppImage, .exe, .msi)
|
||||
|
||||
Download from [Releases](https://gogs.tftsr.com/sarman/tftsr-devops_investigation/releases). All builds are produced natively (no QEMU emulation).
|
||||
Download from [Releases](https://github.com/msicie/apollo_nxt-trcaa/releases). All builds are produced natively (no QEMU emulation).
|
||||
|
||||
## Project Status
|
||||
|
||||
@ -54,8 +54,8 @@ Download from [Releases](https://gogs.tftsr.com/sarman/tftsr-devops_investigatio
|
||||
| Phases 1–8 (Core application) | ✅ Complete |
|
||||
| Phase 9 (History/Search) | 🔲 Pending |
|
||||
| Phase 10 (Integrations) | ✅ Complete — Confluence, ServiceNow, Azure DevOps fully implemented with OAuth2 |
|
||||
| Phase 11 (CI/CD) | ✅ Complete — Gitea Actions fully operational |
|
||||
| Phase 12 (Release packaging) | ✅ linux/amd64 · linux/arm64 (native) · windows/amd64 |
|
||||
| Phase 11 (CI/CD) | ✅ Complete — GitHub Actions fully operational |
|
||||
| Phase 12 (Release packaging) | ✅ linux/amd64 · linux/arm64 · windows/amd64 · macOS ARM64 · macOS Intel |
|
||||
|
||||
## Tech Stack
|
||||
|
||||
@ -69,4 +69,4 @@ Download from [Releases](https://gogs.tftsr.com/sarman/tftsr-devops_investigatio
|
||||
| Secret storage | tauri-plugin-stronghold |
|
||||
| State | Zustand |
|
||||
| Testing | Vitest (13 frontend) + `#[cfg(test)]` (64 Rust tests) |
|
||||
| CI/CD | Gitea Actions (act_runner v0.3.1) + Gitea |
|
||||
| CI/CD | GitHub Actions + ghcr.io |
|
||||
|
||||
@ -1,71 +1,41 @@
|
||||
# Troubleshooting
|
||||
|
||||
## CI/CD — Gitea Actions
|
||||
## CI/CD — GitHub Actions
|
||||
|
||||
### Build Not Triggering After Push
|
||||
|
||||
**Check:**
|
||||
1. Verify the workflow file exists in `.gitea/workflows/` on the pushed branch
|
||||
2. Check the Actions tab at `http://172.0.0.29:3000/sarman/tftsr-devops_investigation/actions`
|
||||
3. Confirm the act_runner is online: `docker logs gitea_act_runner_amd64 --since 5m`
|
||||
1. Verify the workflow file exists in `.github/workflows/` on the pushed branch
|
||||
2. Check the Actions tab at `https://github.com/msicie/apollo_nxt-trcaa/actions`
|
||||
3. Confirm the workflow trigger matches the branch (e.g., `push: branches: [main, 'bug/**']`)
|
||||
|
||||
---
|
||||
|
||||
### Job Container Can't Reach Gitea (`172.0.0.29:3000` blocked)
|
||||
### Jobs Stuck in Queue
|
||||
|
||||
**Cause:** act_runner creates an isolated Docker network per job (when `container:` is specified). Traffic from the job container to `172.0.0.29:3000` is blocked by the host firewall.
|
||||
**Cause:** GitHub Enterprise organizations may restrict which repositories can use
|
||||
GitHub-hosted runners via runner group policies.
|
||||
|
||||
**Fix:** Ensure `container.network: host` is set in the act_runner config AND that `CONFIG_FILE=/data/config.yaml` is in the container's environment:
|
||||
|
||||
```yaml
|
||||
# /docker_mounts/gitea/runner/amd64/config.yaml
|
||||
container:
|
||||
network: "host"
|
||||
```
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml for act-runner-amd64
|
||||
environment:
|
||||
- CONFIG_FILE=/data/config.yaml
|
||||
```
|
||||
|
||||
Also set `capacity: 1` — with capacity > 1, concurrent jobs may not get host networking:
|
||||
|
||||
```yaml
|
||||
runner:
|
||||
capacity: 1
|
||||
```
|
||||
|
||||
Restart runner: `docker restart gitea_act_runner_amd64`
|
||||
**Fix:** Contact your org admin to add the repository to the allowed runner group, or
|
||||
temporarily make the repo public for a one-time bootstrap run (e.g., to build the
|
||||
initial ghcr.io images).
|
||||
|
||||
---
|
||||
|
||||
### `Unable to locate package git` in Rust Job
|
||||
### `manifest unknown` — Image Pull Fails
|
||||
|
||||
**Cause:** `rust:1.88-slim` has an empty apt package cache.
|
||||
**Cause:** The pre-baked CI image (`ghcr.io/msicie/trcaa-linux-amd64:rust1.88-node22`)
|
||||
doesn't exist yet. This happens before the first `build-images.yml` run.
|
||||
|
||||
**Fix:** Always run `apt-get update` before `apt-get install`:
|
||||
```yaml
|
||||
- name: Checkout
|
||||
run: |
|
||||
apt-get update -qq && apt-get install -y -qq git
|
||||
git init
|
||||
git remote add origin http://172.0.0.29:3000/sarman/tftsr-devops_investigation.git
|
||||
git fetch --depth=1 origin $GITHUB_SHA
|
||||
git checkout FETCH_HEAD
|
||||
```
|
||||
**Fix:**
|
||||
1. Go to Actions → Build CI Docker Images → Run workflow
|
||||
2. Wait for all 3 image builds to complete
|
||||
3. Confirm images appear at `https://github.com/orgs/msicie/packages`
|
||||
4. Re-run the failing workflow
|
||||
|
||||
---
|
||||
|
||||
### `exec: "node": executable file not found in $PATH`
|
||||
|
||||
**Cause:** `actions/checkout@v4` is a Node.js action. `rust:1.88-slim` and similar slim images don't have Node.
|
||||
|
||||
**Fix:** Don't use `actions/checkout@v4` — use direct git commands instead (see above).
|
||||
|
||||
---
|
||||
|
||||
### Job Skipped (status 6) on Tag Push
|
||||
### Job Skipped on Tag Push
|
||||
|
||||
**Cause:** Pattern matching issue with `on: push: tags:`. Use unquoted glob in the workflow:
|
||||
|
||||
@ -92,29 +62,17 @@ on:
|
||||
|
||||
---
|
||||
|
||||
### `CI=woodpecker` Rejected by `cargo tauri build`
|
||||
|
||||
**Cause:** CI runners set `CI=woodpecker` (string). Tauri CLI expects `true`/`false`.
|
||||
|
||||
**Fix:** Prefix the build command:
|
||||
```yaml
|
||||
- run: CI=true cargo tauri build --target $TARGET
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Release Artifacts Not Uploaded
|
||||
|
||||
**Cause 1:** `RELEASE_TOKEN` secret not set or expired.
|
||||
```bash
|
||||
# Recreate via admin CLI:
|
||||
docker exec -u git gitea_app gitea admin user generate-access-token \
|
||||
--username sarman --token-name gitea-ci-token --raw \
|
||||
--scopes 'write:repository,read:user'
|
||||
# Add the token as RELEASE_TOKEN in repo Settings > Actions > Secrets
|
||||
```
|
||||
**Cause:** `GITHUB_TOKEN` permissions insufficient. The `release.yml` workflow requires
|
||||
`contents: write` to create releases and upload assets.
|
||||
|
||||
**Cause 2:** Each build job uploads its own artifacts independently. All jobs require host network on the runner (see above).
|
||||
Verify the permissions block:
|
||||
```yaml
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@ -158,7 +116,7 @@ Auto-fix: `cargo clippy --manifest-path src-tauri/Cargo.toml --fix --allow-dirty
|
||||
**Fix (Fedora/RHEL):**
|
||||
```bash
|
||||
sudo dnf install -y glib2-devel gtk3-devel webkit2gtk4.1-devel \
|
||||
libsoup3-devel openssl-devel librsvg2-devel
|
||||
libsoup3-devel openssl-devel librsvg2-dev
|
||||
```
|
||||
|
||||
**Fix (Debian/Ubuntu):**
|
||||
@ -169,6 +127,17 @@ sudo apt-get install -y libwebkit2gtk-4.1-dev libssl-dev libgtk-3-dev \
|
||||
|
||||
---
|
||||
|
||||
### Windows Cross-Compile: DLL Export Ordinal Too Large
|
||||
`/usr/bin/x86_64-w64-mingw32-ld: error: export ordinal too large: 106290`
|
||||
|
||||
Fix: `src-tauri/.cargo/config.toml`:
|
||||
```toml
|
||||
[target.x86_64-pc-windows-gnu]
|
||||
rustflags = ["-C", "link-arg=-Wl,--exclude-all-symbols"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database
|
||||
|
||||
### DB Won't Open in Production
|
||||
@ -220,27 +189,3 @@ const title = detail.title;
|
||||
Common causes:
|
||||
- Mocked `invoke()` return type doesn't match updated command signature
|
||||
- `sessionStore` state not reset between tests (call `store.reset()` in `beforeEach`)
|
||||
|
||||
---
|
||||
|
||||
## Gitea
|
||||
|
||||
### API Token Authentication
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: token <token_value>" http://172.0.0.29:3000/api/v1/user
|
||||
```
|
||||
|
||||
Create tokens in Gitea Settings > Applications > Access Tokens, or via admin CLI:
|
||||
```bash
|
||||
docker exec -u git gitea_app gitea admin user generate-access-token \
|
||||
--username sarman --token-name mytoken --raw --scopes 'read:user,write:repository'
|
||||
```
|
||||
|
||||
### PostgreSQL Access
|
||||
|
||||
```bash
|
||||
docker exec gogs_postgres_db psql -U gogs -d gogsdb -c "SELECT id, lower_name, is_private FROM repository;"
|
||||
```
|
||||
|
||||
Database is named `gogsdb`. The PostgreSQL instance uses SCRAM-SHA-256 auth (MD5 also configured for the `gogs` user for compatibility with older clients).
|
||||
|
||||
@ -4,11 +4,11 @@ import path from "node:path";
|
||||
|
||||
const autoTagWorkflowPath = path.resolve(
|
||||
process.cwd(),
|
||||
".gitea/workflows/auto-tag.yml",
|
||||
".github/workflows/release.yml",
|
||||
);
|
||||
|
||||
describe("auto-tag workflow release triggering", () => {
|
||||
it("creates tags via git push instead of Gitea tag API", () => {
|
||||
it("creates tags via git push instead of API call", () => {
|
||||
const workflow = readFileSync(autoTagWorkflowPath, "utf-8");
|
||||
|
||||
expect(workflow).toContain("git push origin \"refs/tags/$NEXT\"");
|
||||
@ -23,7 +23,13 @@ describe("auto-tag workflow release triggering", () => {
|
||||
expect(workflow).toContain("build-macos-arm64:");
|
||||
expect(workflow).toContain("build-linux-arm64:");
|
||||
expect(workflow).toContain("needs: autotag");
|
||||
expect(workflow).toContain("TAG=$(curl -s \"$API/tags?limit=50\"");
|
||||
expect(workflow).toContain("ERROR: Could not resolve release tag from repository tags.");
|
||||
expect(workflow).toContain("git tag --sort=-version:refname");
|
||||
});
|
||||
|
||||
it("also includes macOS Intel build job", () => {
|
||||
const workflow = readFileSync(autoTagWorkflowPath, "utf-8");
|
||||
|
||||
expect(workflow).toContain("build-macos-intel:");
|
||||
expect(workflow).toContain("macos-13");
|
||||
});
|
||||
});
|
||||
|
||||
@ -100,10 +100,10 @@ describe("Dockerfile.linux-arm64", () => {
|
||||
// ─── build-images.yml workflow ───────────────────────────────────────────────
|
||||
|
||||
describe("build-images.yml workflow", () => {
|
||||
const wf = readFile(".gitea/workflows/build-images.yml");
|
||||
const wf = readFile(".github/workflows/build-images.yml");
|
||||
|
||||
it("triggers on changes to .docker/ files on master", () => {
|
||||
expect(wf).toContain("- master");
|
||||
it("triggers on changes to .docker/ files on main", () => {
|
||||
expect(wf).toContain("- main");
|
||||
expect(wf).toContain("- '.docker/**'");
|
||||
});
|
||||
|
||||
@ -111,38 +111,28 @@ describe("build-images.yml workflow", () => {
|
||||
expect(wf).toContain("workflow_dispatch:");
|
||||
});
|
||||
|
||||
it("does not explicitly mount the Docker socket (act_runner mounts it automatically)", () => {
|
||||
// act_runner already mounts /var/run/docker.sock; an explicit options: mount
|
||||
// causes a 'Duplicate mount point' error and must not be present.
|
||||
expect(wf).not.toContain("-v /var/run/docker.sock:/var/run/docker.sock");
|
||||
});
|
||||
|
||||
it("authenticates to the local Gitea registry before pushing", () => {
|
||||
expect(wf).toContain("docker login");
|
||||
it("authenticates to ghcr.io before pushing", () => {
|
||||
expect(wf).toContain("docker login ghcr.io");
|
||||
expect(wf).toContain("--password-stdin");
|
||||
expect(wf).toContain("172.0.0.29:3000");
|
||||
expect(wf).toContain("ghcr.io");
|
||||
});
|
||||
|
||||
it("builds and pushes all three platform images", () => {
|
||||
expect(wf).toContain("trcaa-linux-amd64:rust1.88-node22");
|
||||
expect(wf).toContain("trcaa-windows-cross:rust1.88-node22");
|
||||
expect(wf).toContain("trcaa-linux-arm64:rust1.88-node22");
|
||||
it("builds and pushes all three platform images to ghcr.io", () => {
|
||||
expect(wf).toContain("ghcr.io/msicie/trcaa-linux-amd64:rust1.88-node22");
|
||||
expect(wf).toContain("ghcr.io/msicie/trcaa-windows-cross:rust1.88-node22");
|
||||
expect(wf).toContain("ghcr.io/msicie/trcaa-linux-arm64:rust1.88-node22");
|
||||
});
|
||||
|
||||
it("uses alpine:latest with docker-cli (not docker:24-cli which triggers duplicate socket mount in act_runner)", () => {
|
||||
// act_runner v0.3.1 special-cases docker:* images and adds the socket bind;
|
||||
// combined with its global socket bind this causes a 'Duplicate mount point' error.
|
||||
expect(wf).toContain("alpine:latest");
|
||||
expect(wf).toContain("docker-cli");
|
||||
expect(wf).not.toContain("docker:24-cli");
|
||||
});
|
||||
|
||||
it("runs all three build jobs on linux-amd64 runner", () => {
|
||||
const matches = wf.match(/runs-on: linux-amd64/g) ?? [];
|
||||
it("runs all three build jobs on ubuntu-latest runner", () => {
|
||||
const matches = wf.match(/runs-on: ubuntu-latest/g) ?? [];
|
||||
expect(matches.length).toBeGreaterThanOrEqual(3);
|
||||
});
|
||||
|
||||
it("uses RELEASE_TOKEN secret for registry auth", () => {
|
||||
expect(wf).toContain("secrets.RELEASE_TOKEN");
|
||||
it("uses GITHUB_TOKEN for registry auth", () => {
|
||||
expect(wf).toContain("secrets.GITHUB_TOKEN");
|
||||
});
|
||||
|
||||
it("grants packages write permission", () => {
|
||||
expect(wf).toContain("packages: write");
|
||||
});
|
||||
});
|
||||
|
||||
@ -4,7 +4,7 @@ import path from "node:path";
|
||||
|
||||
const autoTagWorkflowPath = path.resolve(
|
||||
process.cwd(),
|
||||
".gitea/workflows/auto-tag.yml",
|
||||
".github/workflows/release.yml",
|
||||
);
|
||||
|
||||
describe("auto-tag release cross-platform artifact handling", () => {
|
||||
@ -18,8 +18,8 @@ describe("auto-tag release cross-platform artifact handling", () => {
|
||||
it("fails linux uploads when no artifacts are found", () => {
|
||||
const workflow = readFileSync(autoTagWorkflowPath, "utf-8");
|
||||
|
||||
expect(workflow).toContain("ERROR: No Linux amd64 artifacts were found to upload.");
|
||||
expect(workflow).toContain("ERROR: No Linux arm64 artifacts were found to upload.");
|
||||
expect(workflow).toContain("ERROR: No Linux amd64 artifacts found.");
|
||||
expect(workflow).toContain("ERROR: No Linux arm64 artifacts found.");
|
||||
expect(workflow).toContain("CI=true npx tauri build");
|
||||
expect(workflow).toContain("find src-tauri/target/aarch64-unknown-linux-gnu/release/bundle -type f");
|
||||
expect(workflow).toContain("CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc");
|
||||
@ -30,18 +30,16 @@ describe("auto-tag release cross-platform artifact handling", () => {
|
||||
it("fails windows uploads when no artifacts are found", () => {
|
||||
const workflow = readFileSync(autoTagWorkflowPath, "utf-8");
|
||||
|
||||
expect(workflow).toContain(
|
||||
"ERROR: No Windows amd64 artifacts were found to upload.",
|
||||
);
|
||||
expect(workflow).toContain("ERROR: No Windows amd64 artifacts found.");
|
||||
});
|
||||
|
||||
it("replaces existing release assets before uploading reruns", () => {
|
||||
const workflow = readFileSync(autoTagWorkflowPath, "utf-8");
|
||||
|
||||
expect(workflow).toContain("Deleting existing asset id=$id name=$NAME before upload...");
|
||||
expect(workflow).toContain("-X DELETE \"$API/releases/$RELEASE_ID/assets/$id\"");
|
||||
expect(workflow).toContain("UPLOAD_NAME=\"linux-amd64-$NAME\"");
|
||||
expect(workflow).toContain("UPLOAD_NAME=\"linux-arm64-$NAME\"");
|
||||
expect(workflow).toContain("gh release delete-asset");
|
||||
expect(workflow).toContain("gh release upload");
|
||||
expect(workflow).toContain("linux-amd64-$(basename");
|
||||
expect(workflow).toContain("linux-arm64-$(basename");
|
||||
});
|
||||
|
||||
it("uses pre-baked Ubuntu 22.04 cross-compiler image for arm64", () => {
|
||||
|
||||
@ -4,7 +4,7 @@ import path from "node:path";
|
||||
|
||||
const autoTagWorkflowPath = path.resolve(
|
||||
process.cwd(),
|
||||
".gitea/workflows/auto-tag.yml",
|
||||
".github/workflows/release.yml",
|
||||
);
|
||||
|
||||
describe("auto-tag release macOS bundle path", () => {
|
||||
|
||||
52
ticket-727547-summary.md
Normal file
52
ticket-727547-summary.md
Normal file
@ -0,0 +1,52 @@
|
||||
# Ticket Summary — ADO #727547
|
||||
|
||||
## Description
|
||||
|
||||
Migrate the `tftsr-devops_investigation` repository from `gogs.tftsr.com/sarman/tftsr-devops_investigation` to `github.com/msicie/apollo_nxt-trcaa`. All CI/CD pipelines, container registries, and PR review tooling must be rebuilt from scratch targeting GitHub. The self-hosted qwen3-coder-next LLM reviewer is replaced by native GitHub Copilot code review. Branch protection on `main` must require passing CI checks and allow owner/CODEOWNERS to bypass.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] Repository exists at `github.com/msicie/apollo_nxt-trcaa` with `main` as the default branch
|
||||
- [x] `.github/workflows/test.yml` runs Rust and frontend tests on every push/PR targeting `main`
|
||||
- [x] `.github/workflows/release.yml` auto-tags, builds for Linux amd64+arm64, Windows amd64, macOS ARM64+Intel, and uploads artifacts to GitHub Releases
|
||||
- [x] `.github/workflows/build-images.yml` builds and pushes pre-baked CI images to `ghcr.io/msicie/`
|
||||
- [x] All Gogs/Gitea pipeline references (`.gitea/workflows/`, `172.0.0.29:3000`, `RELEASE_TOKEN`) removed
|
||||
- [x] PR review uses native GitHub Copilot (no external LLM service required)
|
||||
- [x] `.github/CODEOWNERS` requires `@Shaun-Arman-VFK387_moto` and `@github-copilot` on all PRs
|
||||
- [x] `main` branch protection: requires `rust-test`, `frontend-test`, CODEOWNER review; admins can bypass
|
||||
- [x] Wiki sync targets `github.com/msicie/apollo_nxt-trcaa/wiki`
|
||||
- [ ] One-time: trigger `build-images.yml` to bootstrap `ghcr.io/msicie/` images
|
||||
- [ ] One-time: enable Copilot Code Review in `msicie` org settings
|
||||
|
||||
## Work Implemented
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `.github/workflows/test.yml` | Full rewrite — port from `.gitea/workflows/test.yml` (Gitea); jobs renamed to `rust-test` + `frontend-test` to match branch protection check names; uses `ghcr.io/msicie/trcaa-linux-amd64:rust1.88-node22` |
|
||||
| `.github/workflows/release.yml` | Full rewrite — port from `.gitea/workflows/auto-tag.yml`; GITHUB_TOKEN replaces RELEASE_TOKEN; all Gogs API calls replaced with `gh` CLI; adds `build-macos-intel` job (`macos-13`); `master` refs → `main` |
|
||||
| `.github/workflows/build-images.yml` | Rewrite — login to `ghcr.io` with GITHUB_TOKEN; push images to `ghcr.io/msicie/trcaa-*` |
|
||||
| `.github/workflows/pr-review.yml` | **Deleted** — replaced by native GitHub Copilot |
|
||||
| `.gitea/workflows/` | **Deleted** entire directory |
|
||||
| `.github/CODEOWNERS` | **Created** — `@Shaun-Arman-VFK387_moto @github-copilot` on all paths |
|
||||
| `Makefile` | Replace `GOGS_API`/`GOGS_REPO`/`GOGS_TOKEN` with `GH_REPO`/`GH_TOKEN` and `gh release upload` |
|
||||
| `CLAUDE.md` | Update wiki URL, CI/CD section, branch references (`master` → `main`) |
|
||||
|
||||
**Branch protection on `main`:**
|
||||
- Require PRs before merging
|
||||
- Require 1 approving review
|
||||
- Require CODEOWNER review (`require_code_owner_reviews: true`)
|
||||
- Required status checks: `rust-test`, `frontend-test`
|
||||
- `enforce_admins: false` — owner and admins can bypass
|
||||
|
||||
**PR:** https://github.com/msicie/apollo_nxt-trcaa/pull/1
|
||||
|
||||
## Testing Needed
|
||||
|
||||
1. **Bootstrap Docker images** (one-time): Go to Actions → Build CI Docker Images → Run workflow. Verify all three images appear at `ghcr.io/msicie/`.
|
||||
2. **Enable Copilot Code Review** (one-time manual): `msicie` org Settings → Copilot → Code Review → Enable.
|
||||
3. **Test pipeline**: Verify `rust-test` and `frontend-test` checks appear and pass on PR #1.
|
||||
4. **Copilot review**: After enabling, open a new PR and confirm `@github-copilot` is auto-requested.
|
||||
5. **Branch protection enforcement**: Attempt to merge a PR with a failing check — confirm it is blocked.
|
||||
6. **Owner bypass**: Confirm `@Shaun-Arman-VFK387_moto` can override protection and merge.
|
||||
7. **Release pipeline**: Push a `v*` tag (e.g. `v0.3.10`) and confirm all 5 platform jobs complete and artifacts appear in GitHub Releases.
|
||||
8. **Wiki sync**: Confirm release workflow wiki-sync job pushes content to `github.com/msicie/apollo_nxt-trcaa/wiki`.
|
||||
@ -16,17 +16,17 @@ Conventional Commits throughout but the information was never surfaced to end-us
|
||||
- `cliff.toml` (new) — git-cliff configuration; defines commit parsers, ignored tags,
|
||||
output template, and which commit types appear in the changelog
|
||||
- `CHANGELOG.md` (new) — bootstrapped from all existing tags; maintained by CI going forward
|
||||
- `.gitea/workflows/auto-tag.yml` — new `changelog` job that runs after `autotag`
|
||||
- `.github/workflows/release.yml` — `changelog` job that runs after `autotag`
|
||||
- `docs/wiki/CICD-Pipeline.md` — "Changelog Generation" section added
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] `cliff.toml` present at repo root with working Tera template
|
||||
- [ ] `CHANGELOG.md` present at repo root, bootstrapped from all existing semver tags
|
||||
- [ ] `changelog` job in `auto-tag.yml` runs after `autotag` (parallel with build jobs)
|
||||
- [ ] Each Gitea release body shows grouped conventional-commit entries instead of
|
||||
- [ ] `changelog` job in `release.yml` runs after `autotag` (parallel with build jobs)
|
||||
- [ ] Each GitHub Release body shows grouped conventional-commit entries instead of
|
||||
static `"Release vX.Y.Z"`
|
||||
- [ ] `CHANGELOG.md` committed to master on every release with `[skip ci]` suffix
|
||||
- [ ] `CHANGELOG.md` committed to `main` on every release with `[skip ci]` suffix
|
||||
(no infinite re-trigger loop)
|
||||
- [ ] `CHANGELOG.md` uploaded as a downloadable release asset
|
||||
- [ ] CI/chore/build/test/style commits excluded from changelog output
|
||||
@ -47,15 +47,15 @@ Conventional Commits throughout but the information was never surfaced to end-us
|
||||
- Covers all tagged versions from `v0.1.0` through `v0.2.49` plus `[Unreleased]`
|
||||
- 267 lines covering the full project history
|
||||
|
||||
### `.gitea/workflows/auto-tag.yml` — `changelog` job
|
||||
### `.github/workflows/release.yml` — `changelog` job
|
||||
- `needs: autotag` — waits for the new tag to exist before running
|
||||
- Full history clone: `git fetch --tags --depth=2147483647` so git-cliff can resolve
|
||||
all version boundaries
|
||||
- git-cliff v2.7.0 downloaded as a static x86_64 musl binary (~5 MB); no custom
|
||||
image required
|
||||
- Generates full `CHANGELOG.md` and per-release notes (`--latest --strip all`)
|
||||
- PATCHes the Gitea release body via API with JSON-safe escaping (`jq -Rs .`)
|
||||
- Commits `CHANGELOG.md` to master with `[skip ci]` to prevent workflow re-trigger
|
||||
- Updates the GitHub Release body via `gh release edit` with JSON-safe escaping (`jq -Rs .`)
|
||||
- Commits `CHANGELOG.md` to `main` with `[skip ci]` to prevent workflow re-trigger
|
||||
- Deletes any existing `CHANGELOG.md` asset before re-uploading (rerun-safe)
|
||||
- Runs in parallel with all build jobs — no added wall-clock latency
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ on each job invocation: `apt-get update`, Tauri system libs, Node.js via nodesou
|
||||
the arm64 job — a full `rustup` install. This was the primary cause of slow builds.
|
||||
|
||||
The repository already contains pre-baked builder Docker images (`.docker/Dockerfile.*`) and a
|
||||
`build-images.yml` workflow to push them to the local Gitea registry at `172.0.0.29:3000`.
|
||||
`build-images.yml` workflow to push them to `ghcr.io/msicie/`.
|
||||
These images were never referenced by the actual CI jobs — a critical gap. This work closes
|
||||
that gap and adds `actions/cache@v3` for Cargo and npm.
|
||||
|
||||
@ -15,7 +15,7 @@ that gap and adds `actions/cache@v3` for Cargo and npm.
|
||||
|
||||
- [ ] `Dockerfile.linux-amd64` includes `rustfmt` and `clippy` components
|
||||
- [ ] `Dockerfile.linux-arm64` includes `rustfmt` and `clippy` components
|
||||
- [ ] `test.yml` Rust jobs use `172.0.0.29:3000/sarman/trcaa-linux-amd64:rust1.88-node22`
|
||||
- [ ] `test.yml` Rust jobs use `ghcr.io/msicie/trcaa-linux-amd64:rust1.88-node22`
|
||||
- [ ] `test.yml` Rust jobs have no inline `apt-get` or `rustup component add` steps
|
||||
- [ ] `test.yml` Rust jobs include `actions/cache@v3` for `~/.cargo/registry`
|
||||
- [ ] `test.yml` frontend jobs include `actions/cache@v3` for `~/.npm`
|
||||
@ -38,9 +38,9 @@ in the image rather than installing them at job runtime.
|
||||
Added `&& /root/.cargo/bin/rustup component add rustfmt clippy` appended to the
|
||||
existing `rustup` installation RUN command (chained with `&&` to keep it one layer).
|
||||
|
||||
### `.gitea/workflows/test.yml`
|
||||
- **rust-fmt-check**, **rust-clippy**, **rust-tests**: switched container image from
|
||||
`rust:1.88-slim` → `172.0.0.29:3000/sarman/trcaa-linux-amd64:rust1.88-node22`.
|
||||
### `.github/workflows/test.yml`
|
||||
- **rust-test** (fmt + clippy + tests): switched container image from
|
||||
`rust:1.88-slim` → `ghcr.io/msicie/trcaa-linux-amd64:rust1.88-node22`.
|
||||
Removed `apt-get install git` from Checkout steps (git is pre-installed in image).
|
||||
Removed `apt-get install libwebkit2gtk-...` steps.
|
||||
Removed `rustup component add rustfmt` and `rustup component add clippy` steps.
|
||||
@ -49,15 +49,15 @@ existing `rustup` installation RUN command (chained with `&&` to keep it one lay
|
||||
- **frontend-typecheck**, **frontend-tests**: kept `node:22-alpine` image (no change needed).
|
||||
Added `actions/cache@v3` step for `~/.npm` keyed on `package-lock.json` hash.
|
||||
|
||||
### `.gitea/workflows/auto-tag.yml`
|
||||
- **build-linux-amd64**: image `rust:1.88-slim` → `trcaa-linux-amd64:rust1.88-node22`.
|
||||
### `.github/workflows/release.yml`
|
||||
- **build-linux-amd64**: image `rust:1.88-slim` → `ghcr.io/msicie/trcaa-linux-amd64:rust1.88-node22`.
|
||||
Removed Checkout apt-get install git, removed entire Install dependencies step.
|
||||
Removed `rustup target add x86_64-unknown-linux-gnu` from Build step. Added cargo + npm cache.
|
||||
- **build-windows-amd64**: image `rust:1.88-slim` → `trcaa-windows-cross:rust1.88-node22`.
|
||||
- **build-windows-amd64**: image `rust:1.88-slim` → `ghcr.io/msicie/trcaa-windows-cross:rust1.88-node22`.
|
||||
Removed Checkout apt-get install git, removed entire Install dependencies step.
|
||||
Removed `rustup target add x86_64-pc-windows-gnu` from Build step.
|
||||
Added cargo (with `-windows-` suffix key to avoid collision) + npm cache.
|
||||
- **build-linux-arm64**: image `ubuntu:22.04` → `trcaa-linux-arm64:rust1.88-node22`.
|
||||
- **build-linux-arm64**: image `ubuntu:22.04` → `ghcr.io/msicie/trcaa-linux-arm64:rust1.88-node22`.
|
||||
Removed Checkout apt-get install git, removed entire Install dependencies step (~40 lines).
|
||||
Removed `. "$HOME/.cargo/env"` (PATH already set via `ENV` in Dockerfile).
|
||||
Removed `rustup target add aarch64-unknown-linux-gnu` from Build step.
|
||||
@ -67,7 +67,7 @@ existing `rustup` installation RUN command (chained with `&&` to keep it one lay
|
||||
Added two new sections before the Test Pipeline section:
|
||||
- **Pre-baked Builder Images**: table of all three images and their contents, rebuild
|
||||
triggers, how-to-rebuild instructions, and the insecure-registries Docker daemon
|
||||
prerequisite for 172.0.0.29.
|
||||
prerequisite for ghcr.io access.
|
||||
- **Cargo and npm Caching**: documents the `actions/cache@v3` key patterns in use,
|
||||
including the per-platform cache key suffixes for cross-compile jobs.
|
||||
Updated the Test Pipeline section to reference the correct pre-baked image name.
|
||||
@ -75,21 +75,16 @@ Updated the Release Pipeline job table to show which image each build job uses.
|
||||
|
||||
## Testing Needed
|
||||
|
||||
1. **Pre-build images** (prerequisite): Trigger `build-images.yml` via `workflow_dispatch`
|
||||
on Gitea Actions UI. Confirm all 3 images are pushed and visible in the registry.
|
||||
1. **Pre-build images** (prerequisite): Trigger `build-images.yml` via Actions → Build CI Docker Images → Run workflow. Confirm all 3 images are pushed and visible at `ghcr.io/msicie/`.
|
||||
|
||||
2. **Server prerequisite**: Confirm `/etc/docker/daemon.json` on `172.0.0.29` contains
|
||||
`{"insecure-registries":["172.0.0.29:3000"]}` and Docker was restarted after.
|
||||
|
||||
3. **PR test suite**: Open a PR with these changes. Verify:
|
||||
- All 5 test jobs pass (`rust-fmt-check`, `rust-clippy`, `rust-tests`,
|
||||
`frontend-typecheck`, `frontend-tests`)
|
||||
2. **PR test suite**: Open a PR with these changes. Verify:
|
||||
- `rust-test` and `frontend-test` jobs pass
|
||||
- Job logs show no `apt-get` or `rustup component add` output
|
||||
- Cache hit messages appear on second run
|
||||
|
||||
4. **Release build**: Merge to master. Verify `auto-tag.yml` runs and:
|
||||
- All 3 Linux/Windows build jobs start without Install dependencies step
|
||||
- Artifacts are produced and uploaded to the Gitea release
|
||||
3. **Release build**: Merge to `main`. Verify `release.yml` runs and:
|
||||
- All build jobs start without Install dependencies step
|
||||
- Artifacts are produced and uploaded to the GitHub Release
|
||||
- Total release time is significantly reduced (~7 min vs ~25 min before)
|
||||
|
||||
5. **Expected time savings after caching warms up**:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user