Files
shade 26b724c0f0 feat: manual Gitea build workflow, release publishing, README and docs update
- добавлен ручной 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
2026-10-11 17:20:05 +03:00

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"