# curl로 게시하기

Action은 편의를 위한 것입니다. 모든 단계는 Mocco 공개 API에 보내는 일반 HTTP 요청이므로, 어떤 CI 시스템(또는 내 컴퓨터의 스크립트)에서도 게시할 수 있습니다. `desktop:write` 스코프의 **secret** API 키, 앱 ID(**Setup**에 있음), `curl`, `jq`, `shasum`이 필요합니다.

## 다섯 번의 호출

모든 호출에 `Authorization: Bearer <key>`를 보냅니다. 오류는 `application/problem+json`으로 응답하며, `detail`에 무엇을 고쳐야 하는지 적혀 있습니다.

1. 버전과 파일을 **선언**합니다. `POST /v1/desktop/apps/<app id>/releases`에 버전, 노트, 그리고 파일마다 플랫폼, 종류(`installer` 또는 `updater`), 파일 이름, 콘텐츠 타입, 크기, SHA-256을 보냅니다. updater에는 `.sig` 내용도 함께 보냅니다. 응답은 `201`(이전에 선언한 버전이면 `200`)이고, `releaseId`와 파일마다 업로드 URL을 돌려줍니다. 이미 검증된 파일은 `null`입니다.
2. 각 파일을 URL로 `PUT`해서 **업로드**합니다. URL과 함께 받은 헤더를 그대로 사용하고, 15분 안에 올려야 합니다.
3. **마무리(finalize)**: `POST /v1/desktop/releases/<release id>/finalize`. 응답은 `202`입니다. 업로드가 없거나 불완전하면 파일 이름과 함께 `400`으로 응답합니다. 같은 본문으로 다시 선언해 새 URL을 받고, 업로드한 뒤, 다시 마무리하세요.
4. **대기**: `status`가 `draft`(검증됨) 또는 `failed`(`failureReason`에 어느 파일이 왜 실패했는지 적힘)가 될 때까지 `GET /v1/desktop/releases/<release id>`를 호출합니다.
5. **게시**: `{"channel":"stable"}`로 `POST /v1/desktop/releases/<release id>/publish`를 호출합니다. 응답은 `201`(채널이 이미 그 버전을 제공하고 있으면 `200`)입니다.

게시했거나 회수한 버전은 다시 선언할 수 없습니다(`409 version_exists`). 버전을 올리세요.

## 스크립트

```sh
API=https://www.mocco.club/api/ext/v1
APP=$MOCCO_DESKTOP_APP_ID
AUTH="Authorization: Bearer $MOCCO_API_KEY"
V=0.2.0
DMG=release/App_${V}_aarch64.dmg
TGZ=release/App_${V}_aarch64.app.tar.gz

artifact() { # platform kind file content-type [signature-file]
  jq -n --arg platform "$1" --arg kind "$2" --arg filename "$(basename "$3")" --arg contentType "$4" \
    --argjson size "$(wc -c < "$3" | tr -d ' ')" --arg sha256 "$(shasum -a 256 "$3" | cut -d' ' -f1)" \
    --arg signature "$( [ -n "${5:-}" ] && cat "$5" )" \
    '{platform:$platform, kind:$kind, filename:$filename, contentType:$contentType, size:$size, sha256:$sha256}
     + (if $signature == "" then {} else {signature:$signature} end)'
}
BODY=$(jq -n --arg version "$V" --rawfile notes RELEASE_NOTES.md \
  --argjson dmg "$(artifact darwin-aarch64 installer "$DMG" application/x-apple-diskimage)" \
  --argjson tgz "$(artifact darwin-aarch64 updater "$TGZ" application/gzip "$TGZ.sig")" \
  '{version:$version, notes:$notes, artifacts:[$dmg, $tgz]}')

# 1. Declare.
DECLARED=$(curl -fsS -X POST "$API/desktop/apps/$APP/releases" -H "$AUTH" -H 'content-type: application/json' -d "$BODY")
RELEASE=$(jq -r .releaseId <<<"$DECLARED")

# 2. Upload each file that has a URL (null: already verified).
put() { # platform kind file
  TARGET=$(jq -c --arg p "$1" --arg k "$2" '.uploads[] | select(.platform == $p and .kind == $k) | .upload' <<<"$DECLARED")
  [ "$TARGET" = null ] && return 0
  HEADERS=(); while IFS= read -r h; do HEADERS+=(-H "$h"); done \
    < <(jq -r '.headers | to_entries[] | "\(.key): \(.value)"' <<<"$TARGET")
  curl -fsS -X PUT "${HEADERS[@]}" --upload-file "$3" "$(jq -r .url <<<"$TARGET")"
}
put darwin-aarch64 installer "$DMG"
put darwin-aarch64 updater "$TGZ"

# 3. Finalize, and 4. wait for draft or failed.
curl -fsS -X POST "$API/desktop/releases/$RELEASE/finalize" -H "$AUTH"
while :; do
  STATUS=$(curl -fsS "$API/desktop/releases/$RELEASE" -H "$AUTH")
  case $(jq -r .status <<<"$STATUS") in
    draft) break ;;
    failed) jq -r .failureReason <<<"$STATUS" >&2; exit 1 ;;
    *) sleep 5 ;;
  esac
done

# 5. Publish to stable.
curl -fsS -X POST "$API/desktop/releases/$RELEASE/publish" -H "$AUTH" -H 'content-type: application/json' \
  -d '{"channel":"stable"}'
```

이 스크립트는 macOS와 Linux 러너에서 실행됩니다. `curl -f`는 오류가 나면 단계를 실패시킵니다. 문제의 `detail`을 보려면 `-f`를 빼고 응답 본문을 출력하세요. 같은 버전으로 다시 실행하면 이어서 진행하며, 이미 검증된 파일은 다시 올리지 않습니다.

노트와 회수(yank)를 포함한 전체 API는 [데스크톱 릴리스 레퍼런스](https://github.com/fi-workers/mocco/blob/main/docs/reference/desktop-releases.md)에 있습니다.
