Skip to main content

max / makenotwork

5.5 KB · 138 lines History Blame Raw
1 #!/usr/bin/env bash
2 # ota-publish.sh — Publish an OTA update to makenot.work
3 #
4 # Usage:
5 # ./deploy/ota-publish.sh --slug goingson --version 0.2.2 --target linux --arch x86_64 \
6 # --artifact path/to/bundle.tar.gz [--notes "Bug fixes"] [--signature "..."]
7 #
8 # Environment:
9 # MNW_OTA_EMAIL — MNW account email (or set via --email)
10 # MNW_OTA_PASSWORD — MNW account password (or set via --password)
11 # MNW_OTA_API_KEY — SyncKit app API key (or set via --api-key)
12 # MNW_OTA_SERVER — Server URL (default: https://makenot.work)
13
14 set -euo pipefail
15
16 SERVER="${MNW_OTA_SERVER:-https://makenot.work}"
17 EMAIL="${MNW_OTA_EMAIL:-}"
18 PASSWORD="${MNW_OTA_PASSWORD:-}"
19 API_KEY="${MNW_OTA_API_KEY:-}"
20
21 SLUG=""
22 VERSION=""
23 TARGET=""
24 ARCH=""
25 ARTIFACT=""
26 NOTES=""
27 SIGNATURE=""
28
29 usage() {
30 echo "Usage: $0 --slug SLUG --version X.Y.Z --target OS --arch ARCH --artifact FILE"
31 echo ""
32 echo "Required:"
33 echo " --slug App slug (e.g. goingson, balanced-breakfast, audiofiles)"
34 echo " --version Semver version (e.g. 0.2.2)"
35 echo " --target Target OS: linux, darwin, windows"
36 echo " --arch Architecture: x86_64, aarch64"
37 echo " --artifact Path to the built artifact file"
38 echo ""
39 echo "Optional:"
40 echo " --notes Release notes (default: empty)"
41 echo " --signature Minisign signature for Tauri verification"
42 echo " --email MNW account email (overrides MNW_OTA_EMAIL)"
43 echo " --password MNW account password (overrides MNW_OTA_PASSWORD)"
44 echo " --api-key SyncKit API key (overrides MNW_OTA_API_KEY)"
45 echo " --server Server URL (overrides MNW_OTA_SERVER)"
46 echo ""
47 echo "Environment variables: MNW_OTA_EMAIL, MNW_OTA_PASSWORD, MNW_OTA_API_KEY, MNW_OTA_SERVER"
48 exit 1
49 }
50
51 while [[ $# -gt 0 ]]; do
52 case $1 in
53 --slug) SLUG="$2"; shift 2 ;;
54 --version) VERSION="$2"; shift 2 ;;
55 --target) TARGET="$2"; shift 2 ;;
56 --arch) ARCH="$2"; shift 2 ;;
57 --artifact) ARTIFACT="$2"; shift 2 ;;
58 --notes) NOTES="$2"; shift 2 ;;
59 --signature) SIGNATURE="$2"; shift 2 ;;
60 --email) EMAIL="$2"; shift 2 ;;
61 --password) PASSWORD="$2"; shift 2 ;;
62 --api-key) API_KEY="$2"; shift 2 ;;
63 --server) SERVER="$2"; shift 2 ;;
64 -h|--help) usage ;;
65 *) echo "Unknown option: $1"; usage ;;
66 esac
67 done
68
69 # Validate required args
70 [[ -z "$SLUG" ]] && { echo "Error: --slug required"; usage; }
71 [[ -z "$VERSION" ]] && { echo "Error: --version required"; usage; }
72 [[ -z "$TARGET" ]] && { echo "Error: --target required"; usage; }
73 [[ -z "$ARCH" ]] && { echo "Error: --arch required"; usage; }
74 [[ -z "$ARTIFACT" ]] && { echo "Error: --artifact required"; usage; }
75 [[ -z "$EMAIL" ]] && { echo "Error: MNW_OTA_EMAIL or --email required"; usage; }
76 [[ -z "$PASSWORD" ]] && { echo "Error: MNW_OTA_PASSWORD or --password required"; usage; }
77 [[ -z "$API_KEY" ]] && { echo "Error: MNW_OTA_API_KEY or --api-key required"; usage; }
78
79 [[ ! -f "$ARTIFACT" ]] && { echo "Error: artifact file not found: $ARTIFACT"; exit 1; }
80
81 FILE_SIZE=$(stat -f%z "$ARTIFACT" 2>/dev/null || stat -c%s "$ARTIFACT" 2>/dev/null)
82 echo "Publishing OTA update: $SLUG v$VERSION ($TARGET/$ARCH, ${FILE_SIZE} bytes)"
83
84 # Step 1: Authenticate
85 echo " Authenticating..."
86 AUTH_RESPONSE=$(curl -sf -X POST "$SERVER/api/sync/auth" \
87 -H "Content-Type: application/json" \
88 -d "{\"email\":\"$EMAIL\",\"password\":\"$PASSWORD\",\"api_key\":\"$API_KEY\"}")
89
90 TOKEN=$(echo "$AUTH_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")
91 APP_ID=$(echo "$AUTH_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['app_id'])")
92 echo " Authenticated (app: $APP_ID)"
93
94 # Step 2: Create release
95 echo " Creating release v$VERSION..."
96 RELEASE_BODY=$(python3 -c "import json,sys; print(json.dumps({'version':sys.argv[1],'notes':sys.argv[2],'signature':sys.argv[3]}))" "$VERSION" "$NOTES" "$SIGNATURE")
97 RELEASE_RESPONSE=$(curl -sf -X POST "$SERVER/api/sync/ota/apps/$APP_ID/releases" \
98 -H "Content-Type: application/json" \
99 -H "Authorization: Bearer $TOKEN" \
100 -d "$RELEASE_BODY")
101
102 RELEASE_ID=$(echo "$RELEASE_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
103 echo " Release created: $RELEASE_ID"
104
105 # Step 3: Register artifact and get presigned upload URL
106 echo " Registering artifact ($TARGET/$ARCH, $FILE_SIZE bytes)..."
107 ARTIFACT_BODY=$(printf '{"target":"%s","arch":"%s","file_size":%s}' "$TARGET" "$ARCH" "$FILE_SIZE")
108 ARTIFACT_RESPONSE=$(curl -sf -X POST "$SERVER/api/sync/ota/apps/$APP_ID/releases/$RELEASE_ID/artifacts" \
109 -H "Content-Type: application/json" \
110 -H "Authorization: Bearer $TOKEN" \
111 -d "$ARTIFACT_BODY")
112
113 UPLOAD_URL=$(echo "$ARTIFACT_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['upload_url'])")
114 echo " Got presigned upload URL"
115
116 # Step 4: Upload artifact to S3
117 echo " Uploading artifact..."
118 curl -sf -X PUT "$UPLOAD_URL" \
119 -H "Content-Type: application/octet-stream" \
120 --data-binary "@$ARTIFACT"
121 echo " Upload complete"
122
123 # Step 5: Verify updater endpoint
124 echo " Verifying updater endpoint..."
125 HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
126 "$SERVER/api/sync/ota/$SLUG/$TARGET/$ARCH/0.0.1")
127
128 if [[ "$HTTP_CODE" == "200" ]]; then
129 echo " Updater check returns 200 — update is live"
130 else
131 echo " Warning: updater check returned $HTTP_CODE (expected 200)"
132 echo " The release was created but the updater endpoint may not be serving it yet."
133 fi
134
135 echo ""
136 echo "Published: $SLUG v$VERSION ($TARGET/$ARCH)"
137 echo "Updater URL: $SERVER/api/sync/ota/$SLUG/$TARGET/$ARCH/$VERSION"
138