- добавлен ручной workflow Gitea Actions (.gitea/workflows/build-mqtt-firmwares.yml): сборка MQTT-прошивок на сервере и публикация релиза - добавлен posadmesh-tools/publish-gitea-release.sh для выгрузки собранных файлов в релиз Gitea (идемпотентно, фильтр по треку) - README: происхождение проекта переписано (форк EastMesh, доработка энтузиастов из Сергиева Посада, ориентация на meshcoretel.ru), добавлены раздел «Отличия от upstream MeshCore» и журнал «Изменения в проекте» - убраны упоминания Австралии, ссылки на eastmesh.au и региональные материалы; руководство по прошивке переведено на файлы релизов и esptool.py - posadmesh-build.sh: вывод PlatformIO очищается от \r — на Windows групповые команды сборки не находили ни одной цели; companion-команды удалены, добавлены build-mqtt-firmwares и list-mqtt - release-notes.yml: треки repeater-mqtt*, запись 2026.10.0 дополнена удалением companion и исправлением сборки, companion-wifi помечен как снятый с выпуска - AGENTS.md: правило дописывать изменения в README - .gitignore: .pio-home
150 lines
4.7 KiB
Bash
150 lines
4.7 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# Publish built firmware files to a Gitea release.
|
|
#
|
|
# Usage:
|
|
# posadmesh-tools/publish-gitea-release.sh <tag> <dist-dir> [release-title] [notes-file]
|
|
#
|
|
# Example:
|
|
# GITEA_TOKEN=... posadmesh-tools/publish-gitea-release.sh \
|
|
# repeater-mqtt-v2026.10.0 out "Repeater MQTT v2026.10.0" RELEASE-NOTES.md
|
|
#
|
|
# Environment:
|
|
# GITEA_TOKEN (required) personal access token with write:repository scope
|
|
# GITEA_URL (optional) instance URL, default https://git.meshinfo.ru
|
|
# GITEA_REPO (optional) owner/repo, default shade/MeshCore-Posadmesh
|
|
# ASSET_GLOB (optional) only upload files matching this glob, e.g. '*_repeater_mqtt-v*'
|
|
# (handy when out/ holds several tracks: uplink, espnow, bridge)
|
|
#
|
|
# The script is idempotent: if the release already exists it reuses it and skips
|
|
# assets whose names are already attached.
|
|
#
|
|
# Requires: curl, awk. A working python (python3, python, or the project .venv)
|
|
# is used only to encode the release notes as JSON; without it the release is
|
|
# created with an empty body.
|
|
|
|
set -euo pipefail
|
|
|
|
tag="${1:-}"
|
|
dist="${2:-}"
|
|
title="${3:-$tag}"
|
|
notes_file="${4:-}"
|
|
|
|
if [[ -z "$tag" || -z "$dist" ]]; then
|
|
echo "usage: $0 <tag> <dist-dir> [release-title] [notes-file]" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -d "$dist" ]]; then
|
|
echo "error: distribution directory not found: $dist" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -z "${GITEA_TOKEN:-}" ]]; then
|
|
echo "error: GITEA_TOKEN is not set (needs write access to the repository)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
command -v curl >/dev/null 2>&1 || { echo "error: curl is required" >&2; exit 1; }
|
|
|
|
base="${GITEA_URL:-https://git.meshinfo.ru}"
|
|
repo="${GITEA_REPO:-shade/MeshCore-Posadmesh}"
|
|
api="$base/api/v1/repos/$repo"
|
|
auth="Authorization: token $GITEA_TOKEN"
|
|
work="$(mktemp -d)"
|
|
trap 'rm -rf "$work"' EXIT
|
|
|
|
# pick a python that actually runs (on Windows "python3" may be a Store stub)
|
|
PY=""
|
|
for candidate in .venv/Scripts/python.exe .venv/bin/python python3 python; do
|
|
if command -v "$candidate" >/dev/null 2>&1 || [[ -x "$candidate" ]]; then
|
|
if "$candidate" -c 'import json, sys' >/dev/null 2>&1; then
|
|
PY="$candidate"
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
|
|
echo "repository : $repo"
|
|
echo "tag : $tag"
|
|
echo "files from : $dist"
|
|
|
|
pattern="${ASSET_GLOB:-*}"
|
|
if [[ "$pattern" != "*" ]]; then
|
|
echo "asset glob : $pattern"
|
|
fi
|
|
|
|
assets=()
|
|
while IFS= read -r -d '' f; do
|
|
assets+=("$f")
|
|
done < <(find "$dist" -maxdepth 1 -type f -name "$pattern" \( -name '*.bin' -o -name '*.uf2' -o -name '*.zip' -o -name '*.elf' \) -print0 | sort -z)
|
|
|
|
if [[ ${#assets[@]} -eq 0 ]]; then
|
|
echo "error: no matching .bin/.uf2/.zip/.elf files found in $dist (glob: $pattern)" >&2
|
|
exit 1
|
|
fi
|
|
echo "assets : ${#assets[@]}"
|
|
|
|
body_json='""'
|
|
if [[ -n "$notes_file" && -f "$notes_file" ]]; then
|
|
if [[ -n "$PY" ]]; then
|
|
body_json="$("$PY" -c 'import json,sys; print(json.dumps(open(sys.argv[1], encoding="utf-8").read()))' "$notes_file")"
|
|
else
|
|
echo "warning: no usable python found, release notes will be empty" >&2
|
|
fi
|
|
fi
|
|
|
|
payload_file="$work/release.json"
|
|
printf '{"tag_name":"%s","name":"%s","body":%s,"draft":false,"prerelease":false}' \
|
|
"$tag" "$title" "$body_json" > "$payload_file"
|
|
|
|
code=$(curl -sS -o "$work/release.json" -w '%{http_code}' \
|
|
-X POST "$api/releases" -H "$auth" -H 'Content-Type: application/json' \
|
|
--data-binary "@$payload_file" || true)
|
|
|
|
release_id=""
|
|
if [[ "$code" == "201" ]]; then
|
|
release_id=$(grep -o '"id":[0-9]*' "$work/release.json" | head -n 1 | cut -d: -f2)
|
|
echo "created release id=$release_id"
|
|
else
|
|
echo "release create returned HTTP $code, looking up the existing release"
|
|
release_id=$(curl -sS "$api/releases/tags/$tag" -H "$auth" \
|
|
| grep -o '"id":[0-9]*' | head -n 1 | cut -d: -f2 || true)
|
|
if [[ -z "$release_id" ]]; then
|
|
echo "error: cannot create or find a release for tag $tag" >&2
|
|
head -c 400 "$work/release.json" >&2 || true
|
|
echo >&2
|
|
exit 1
|
|
fi
|
|
echo "reusing release id=$release_id"
|
|
fi
|
|
|
|
existing=$(curl -sS "$api/releases/$release_id/assets" -H "$auth" \
|
|
| grep -o '"name":"[^"]*"' | cut -d'"' -f4 || true)
|
|
|
|
uploaded=0
|
|
skipped=0
|
|
for f in "${assets[@]}"; do
|
|
name="$(basename "$f")"
|
|
if grep -qxF "$name" <<<"$existing"; then
|
|
echo "skip $name (already attached)"
|
|
skipped=$((skipped + 1))
|
|
continue
|
|
fi
|
|
code=$(curl -sS -o "$work/asset.json" -w '%{http_code}' \
|
|
-X POST "$api/releases/$release_id/assets?name=$name" \
|
|
-H "$auth" -F "attachment=@$f" || true)
|
|
if [[ "$code" == "201" ]]; then
|
|
echo "upload $name"
|
|
uploaded=$((uploaded + 1))
|
|
else
|
|
echo "error: upload of $name failed with HTTP $code" >&2
|
|
head -c 400 "$work/asset.json" >&2 || true
|
|
echo >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
echo "done: uploaded=$uploaded skipped=$skipped"
|
|
echo "release: $base/$repo/releases/tag/$tag"
|