gogs_release.sh 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. # Create and publish a Gogs release through the web form.
  4. #
  5. # Gogs's API only exposes GET /repos/:owner/:repo/releases; POST returns 404 on
  6. # this instance. The web form still accepts a normal user/password session, so
  7. # this helper logs in locally and posts the release. Credentials are read from
  8. # a mode-600 file or prompt and are never written to the repository or printed.
  9. #
  10. # Usage:
  11. # ./tool/gogs_release.sh <tag> <title> <body-file>
  12. #
  13. # Example:
  14. # ./tool/gogs_release.sh v1.0.7 "CarDash 1.0.7 — Truly disable Impeller" releases/1.0.7.md
  15. #
  16. # Optional environment:
  17. # GOGS_URL default https://git.krasi.net
  18. # GOGS_REPO default kkazakov/car-interface-android
  19. # GOGS_TARGET default main
  20. # GOGS_USER skip the username prompt
  21. # GOGS_PASSWORD skip the password prompt (prefer the credentials file)
  22. # GOGS_CREDENTIALS credentials file path (default: $HOME/.config/cardash/gogs_credentials,
  23. # then android/keystore/gogs_credentials)
  24. #
  25. # Credentials file format (mode 600, gitignored):
  26. # username=kkazakov
  27. # password=...
  28. #
  29. # If 2FA is enabled, log in through the browser and use the web UI instead.
  30. BASE="${GOGS_URL:-https://git.krasi.net}"
  31. REPO="${GOGS_REPO:-kkazakov/car-interface-android}"
  32. TARGET="${GOGS_TARGET:-main}"
  33. if [[ $# -lt 2 || $# -gt 3 ]]; then
  34. echo "Usage: $0 <tag> <title> [body-file]" >&2
  35. echo "Example: $0 v1.0.7 'CarDash 1.0.7' releases/1.0.7.md" >&2
  36. exit 2
  37. fi
  38. TAG="$1"
  39. TITLE="$2"
  40. BODY_FILE="${3:-releases/${TAG#v}.md}"
  41. if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
  42. echo "Tag must look like v1.2.3 (got: $TAG)." >&2
  43. exit 2
  44. fi
  45. if [[ ! -f "$BODY_FILE" ]]; then
  46. echo "Release body file not found: $BODY_FILE" >&2
  47. exit 2
  48. fi
  49. USER_NAME="${GOGS_USER:-}"
  50. PASSWORD="${GOGS_PASSWORD:-}"
  51. CRED_FILE="${GOGS_CREDENTIALS:-}"
  52. if [[ -z "$CRED_FILE" ]]; then
  53. for candidate in \
  54. "$HOME/.config/cardash/gogs_credentials" \
  55. "android/keystore/gogs_credentials"; do
  56. if [[ -f "$candidate" ]]; then
  57. CRED_FILE="$candidate"
  58. break
  59. fi
  60. done
  61. fi
  62. if [[ -n "$CRED_FILE" ]]; then
  63. if [[ ! -r "$CRED_FILE" ]]; then
  64. echo "Credentials file is not readable: $CRED_FILE" >&2
  65. exit 2
  66. fi
  67. if [[ -z "$USER_NAME" ]]; then
  68. USER_NAME="$(sed -n 's/^username=//p' "$CRED_FILE" | head -n 1)"
  69. fi
  70. if [[ -z "$PASSWORD" ]]; then
  71. PASSWORD="$(sed -n 's/^password=//p' "$CRED_FILE" | head -n 1)"
  72. fi
  73. fi
  74. if [[ -z "$USER_NAME" ]]; then
  75. read -r -p "Gogs user: " USER_NAME
  76. fi
  77. if [[ -z "$PASSWORD" ]]; then
  78. read -r -s -p "Gogs password for $USER_NAME: " PASSWORD
  79. echo
  80. fi
  81. if [[ -z "$PASSWORD" ]]; then
  82. echo "No password supplied." >&2
  83. exit 2
  84. fi
  85. umask 077
  86. TMP_DIR="$(mktemp -d)"
  87. cleanup() {
  88. rm -rf "$TMP_DIR"
  89. }
  90. trap cleanup EXIT
  91. COOKIES="$TMP_DIR/cookies.txt"
  92. PASS_FILE="$TMP_DIR/password"
  93. printf '%s' "$PASSWORD" > "$PASS_FILE"
  94. unset PASSWORD
  95. csrf_from() {
  96. sed -n 's/.*name="_csrf" value="\([^"]*\)".*/\1/p' "$1" | head -n 1
  97. }
  98. LOGIN_PAGE="$TMP_DIR/login.html"
  99. if ! curl -fsS -c "$COOKIES" "$BASE/user/login" -o "$LOGIN_PAGE"; then
  100. echo "Could not reach $BASE/user/login." >&2
  101. exit 1
  102. fi
  103. LOGIN_CSRF="$(csrf_from "$LOGIN_PAGE")"
  104. if [[ -z "$LOGIN_CSRF" ]]; then
  105. echo "Could not read the Gogs login form. Is GOGS_URL correct?" >&2
  106. exit 1
  107. fi
  108. LOGIN_CODE="$(curl -sS -b "$COOKIES" -c "$COOKIES" \
  109. -o "$TMP_DIR/login-result.html" -w '%{http_code}' \
  110. -X POST "$BASE/user/login" \
  111. --data-urlencode "_csrf=$LOGIN_CSRF" \
  112. --data-urlencode "user_name=$USER_NAME" \
  113. --data-urlencode "password@$PASS_FILE" \
  114. --data-urlencode 'remember=on')"
  115. if [[ "$LOGIN_CODE" != "302" ]]; then
  116. echo "Gogs login failed (HTTP $LOGIN_CODE). Check the username/password or 2FA." >&2
  117. exit 1
  118. fi
  119. NEW_PAGE="$TMP_DIR/new-release.html"
  120. if ! curl -fsS -b "$COOKIES" -c "$COOKIES" \
  121. "$BASE/$REPO/releases/new" -o "$NEW_PAGE"; then
  122. echo "Could not open $BASE/$REPO/releases/new. Is the session valid?" >&2
  123. exit 1
  124. fi
  125. if ! grep -q 'name="tag_name"' "$NEW_PAGE"; then
  126. echo "The release form did not load; the account may lack write access." >&2
  127. exit 1
  128. fi
  129. RELEASE_CSRF="$(csrf_from "$NEW_PAGE")"
  130. if [[ -z "$RELEASE_CSRF" ]]; then
  131. echo "Could not read the release form CSRF token." >&2
  132. exit 1
  133. fi
  134. RELEASE_CODE="$(curl -sS -b "$COOKIES" -c "$COOKIES" \
  135. -D "$TMP_DIR/release-headers.txt" -o "$TMP_DIR/release-result.html" \
  136. -w '%{http_code}' \
  137. -X POST "$BASE/$REPO/releases/new" \
  138. --data-urlencode "_csrf=$RELEASE_CSRF" \
  139. --data-urlencode "tag_name=$TAG" \
  140. --data-urlencode "tag_target=$TARGET" \
  141. --data-urlencode "title=$TITLE" \
  142. --data-urlencode "content@$BODY_FILE")"
  143. if [[ "$RELEASE_CODE" != "302" ]]; then
  144. echo "Gogs rejected the release (HTTP $RELEASE_CODE)." >&2
  145. echo "Check that $TAG does not already exist and that $TARGET is a branch." >&2
  146. exit 1
  147. fi
  148. if grep -q "\"tag_name\": *\"$TAG\"" <(curl -fsS "$BASE/api/v1/repos/$REPO/releases"); then
  149. echo "Published $TAG"
  150. else
  151. echo "Release request accepted; verify at $BASE/$REPO/releases"
  152. fi