| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- #!/usr/bin/env bash
- set -euo pipefail
- # Create and publish a Gogs release through the web form.
- #
- # Gogs's API only exposes GET /repos/:owner/:repo/releases; POST returns 404 on
- # this instance. The web form still accepts a normal user/password session, so
- # this helper logs in locally and posts the release. Credentials are read from
- # a mode-600 file or prompt and are never written to the repository or printed.
- #
- # Usage:
- # ./tool/gogs_release.sh <tag> <title> <body-file>
- #
- # Example:
- # ./tool/gogs_release.sh v1.0.7 "CarDash 1.0.7 — Truly disable Impeller" releases/1.0.7.md
- #
- # Optional environment:
- # GOGS_URL default https://git.krasi.net
- # GOGS_REPO default kkazakov/car-interface-android
- # GOGS_TARGET default main
- # GOGS_USER skip the username prompt
- # GOGS_PASSWORD skip the password prompt (prefer the credentials file)
- # GOGS_CREDENTIALS credentials file path (default: $HOME/.config/cardash/gogs_credentials,
- # then android/keystore/gogs_credentials)
- #
- # Credentials file format (mode 600, gitignored):
- # username=kkazakov
- # password=...
- #
- # If 2FA is enabled, log in through the browser and use the web UI instead.
- BASE="${GOGS_URL:-https://git.krasi.net}"
- REPO="${GOGS_REPO:-kkazakov/car-interface-android}"
- TARGET="${GOGS_TARGET:-main}"
- if [[ $# -lt 2 || $# -gt 3 ]]; then
- echo "Usage: $0 <tag> <title> [body-file]" >&2
- echo "Example: $0 v1.0.7 'CarDash 1.0.7' releases/1.0.7.md" >&2
- exit 2
- fi
- TAG="$1"
- TITLE="$2"
- BODY_FILE="${3:-releases/${TAG#v}.md}"
- if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
- echo "Tag must look like v1.2.3 (got: $TAG)." >&2
- exit 2
- fi
- if [[ ! -f "$BODY_FILE" ]]; then
- echo "Release body file not found: $BODY_FILE" >&2
- exit 2
- fi
- USER_NAME="${GOGS_USER:-}"
- PASSWORD="${GOGS_PASSWORD:-}"
- CRED_FILE="${GOGS_CREDENTIALS:-}"
- if [[ -z "$CRED_FILE" ]]; then
- for candidate in \
- "$HOME/.config/cardash/gogs_credentials" \
- "android/keystore/gogs_credentials"; do
- if [[ -f "$candidate" ]]; then
- CRED_FILE="$candidate"
- break
- fi
- done
- fi
- if [[ -n "$CRED_FILE" ]]; then
- if [[ ! -r "$CRED_FILE" ]]; then
- echo "Credentials file is not readable: $CRED_FILE" >&2
- exit 2
- fi
- if [[ -z "$USER_NAME" ]]; then
- USER_NAME="$(sed -n 's/^username=//p' "$CRED_FILE" | head -n 1)"
- fi
- if [[ -z "$PASSWORD" ]]; then
- PASSWORD="$(sed -n 's/^password=//p' "$CRED_FILE" | head -n 1)"
- fi
- fi
- if [[ -z "$USER_NAME" ]]; then
- read -r -p "Gogs user: " USER_NAME
- fi
- if [[ -z "$PASSWORD" ]]; then
- read -r -s -p "Gogs password for $USER_NAME: " PASSWORD
- echo
- fi
- if [[ -z "$PASSWORD" ]]; then
- echo "No password supplied." >&2
- exit 2
- fi
- umask 077
- TMP_DIR="$(mktemp -d)"
- cleanup() {
- rm -rf "$TMP_DIR"
- }
- trap cleanup EXIT
- COOKIES="$TMP_DIR/cookies.txt"
- PASS_FILE="$TMP_DIR/password"
- printf '%s' "$PASSWORD" > "$PASS_FILE"
- unset PASSWORD
- csrf_from() {
- sed -n 's/.*name="_csrf" value="\([^"]*\)".*/\1/p' "$1" | head -n 1
- }
- LOGIN_PAGE="$TMP_DIR/login.html"
- if ! curl -fsS -c "$COOKIES" "$BASE/user/login" -o "$LOGIN_PAGE"; then
- echo "Could not reach $BASE/user/login." >&2
- exit 1
- fi
- LOGIN_CSRF="$(csrf_from "$LOGIN_PAGE")"
- if [[ -z "$LOGIN_CSRF" ]]; then
- echo "Could not read the Gogs login form. Is GOGS_URL correct?" >&2
- exit 1
- fi
- LOGIN_CODE="$(curl -sS -b "$COOKIES" -c "$COOKIES" \
- -o "$TMP_DIR/login-result.html" -w '%{http_code}' \
- -X POST "$BASE/user/login" \
- --data-urlencode "_csrf=$LOGIN_CSRF" \
- --data-urlencode "user_name=$USER_NAME" \
- --data-urlencode "password@$PASS_FILE" \
- --data-urlencode 'remember=on')"
- if [[ "$LOGIN_CODE" != "302" ]]; then
- echo "Gogs login failed (HTTP $LOGIN_CODE). Check the username/password or 2FA." >&2
- exit 1
- fi
- NEW_PAGE="$TMP_DIR/new-release.html"
- if ! curl -fsS -b "$COOKIES" -c "$COOKIES" \
- "$BASE/$REPO/releases/new" -o "$NEW_PAGE"; then
- echo "Could not open $BASE/$REPO/releases/new. Is the session valid?" >&2
- exit 1
- fi
- if ! grep -q 'name="tag_name"' "$NEW_PAGE"; then
- echo "The release form did not load; the account may lack write access." >&2
- exit 1
- fi
- RELEASE_CSRF="$(csrf_from "$NEW_PAGE")"
- if [[ -z "$RELEASE_CSRF" ]]; then
- echo "Could not read the release form CSRF token." >&2
- exit 1
- fi
- RELEASE_CODE="$(curl -sS -b "$COOKIES" -c "$COOKIES" \
- -D "$TMP_DIR/release-headers.txt" -o "$TMP_DIR/release-result.html" \
- -w '%{http_code}' \
- -X POST "$BASE/$REPO/releases/new" \
- --data-urlencode "_csrf=$RELEASE_CSRF" \
- --data-urlencode "tag_name=$TAG" \
- --data-urlencode "tag_target=$TARGET" \
- --data-urlencode "title=$TITLE" \
- --data-urlencode "content@$BODY_FILE")"
- if [[ "$RELEASE_CODE" != "302" ]]; then
- echo "Gogs rejected the release (HTTP $RELEASE_CODE)." >&2
- echo "Check that $TAG does not already exist and that $TARGET is a branch." >&2
- exit 1
- fi
- if grep -q "\"tag_name\": *\"$TAG\"" <(curl -fsS "$BASE/api/v1/repos/$REPO/releases"); then
- echo "Published $TAG"
- else
- echo "Release request accepted; verify at $BASE/$REPO/releases"
- fi
|