Commit c2c5268c22
c2c5268c22f581c71a5df2ad523d6785556a285a
parent: eb50ddd595
Verified · cmc
cmc <hello@cleberg.net> · 2026-10-18 00:32 UTC
Deploy backend and point the app at it
- server/wrangler.jsonc: D1 database id for the deployed "moonrise".
- Optional LL2_API_KEY secret sent as "Authorization: Token"; anonymous
requests from Workers hit LL2's per-IP limit (429).
- App: Debug and Release use https://moonrise-server.audit-labs.workers.dev.
- READMEs and PLAN.org: deployment state.
Layout: unified · split
App/README.org
+4
| @@ -11,3 +11,7 @@ open Moonrise.xcodeproj |
| 11 | 11 | Simulator: an iOS 26+ runtime is required. Push registration needs a |
| 12 | 12 | signed build (team =ZCNAX3VL9D=); the APNs device token is logged under |
| 13 | 13 | subsystem =sh.krz.moonrise=. |
| 14 | |
| 15 | Backend: both configurations use https://moonrise-server.audit-labs.workers.dev |
| 16 | (=MOONRISE_API_URL= in =project.yml=). For a local Worker, set it to |
| 17 | =http://localhost:8787= and regenerate. |
App/project.yml
+2 −2
| @@ -32,9 +32,9 @@ targets: |
| 32 | 32 | settings: |
| 33 | 33 | configs: |
| 34 | 34 | Debug: |
| 35 | | MOONRISE_API_URL: http://localhost:8787 |
| 35 | MOONRISE_API_URL: https://moonrise-server.audit-labs.workers.dev |
| 36 | 36 | Release: |
| 37 | | MOONRISE_API_URL: "" |
| 37 | MOONRISE_API_URL: https://moonrise-server.audit-labs.workers.dev |
| 38 | 38 | base: |
| 39 | 39 | PRODUCT_BUNDLE_IDENTIFIER: sh.krz.moonrise |
| 40 | 40 | DEVELOPMENT_TEAM: ZCNAX3VL9D |
PLAN.org
+11
| @@ -374,6 +374,17 @@ MR !11. |
| 374 | 374 | - Not done: tap a planet or probe for its Library page, Earth–Moon inset, |
| 375 | 375 | active missions (Juno, Europa Clipper, JUICE, Psyche, Parker, Lucy). |
| 376 | 376 | |
| 377 | ** Deployment (2026-10-18) |
| 378 | - Worker =moonrise-server= at https://moonrise-server.audit-labs.workers.dev, |
| 379 | D1 =moonrise=, crons =*/5= (SWPC) and =*/10= (LL2), APNS_KEY set. |
| 380 | - SWPC polling confirmed in production (=/v1/today= serving Kp). |
| 381 | - LL2 from Cloudflare returned 429 on the first run: the anonymous limit |
| 382 | is per IP and Workers share egress IPs. Needs an LL2 API key |
| 383 | (=LL2_API_KEY= secret, supported). Launches stay empty until then. |
| 384 | - App Debug and Release builds use the deployed URL. |
| 385 | - The app does not register devices with the backend yet, so no aurora |
| 386 | pushes are sent. |
| 387 | |
| 377 | 388 | * roadmap |
| 378 | 389 | ** v1: now |
| 379 | 390 | Sky, System, Sun, Launches, Library core (above). |
server/README.org
+10
| @@ -15,6 +15,9 @@ APNs cannot be reached from =wrangler dev= (no HTTP/2); test pushes |
| 15 | 15 | against a deployed Worker. |
| 16 | 16 | |
| 17 | 17 | * deploy |
| 18 | Deployed 2026-10-17: https://moonrise-server.audit-labs.workers.dev |
| 19 | (D1 =moonrise= 54a0c50d-2150-47ae-aa22-449e8204ad7b, APNS_KEY set). |
| 20 | |
| 18 | 21 | #+begin_src sh |
| 19 | 22 | wrangler d1 create moonrise # put the id in wrangler.jsonc |
| 20 | 23 | wrangler d1 migrations apply moonrise --remote |
| @@ -22,6 +25,13 @@ wrangler deploy |
| 22 | 25 | wrangler secret put APNS_KEY < AuthKey_644XS4F2RG.p8 |
| 23 | 26 | #+end_src |
| 24 | 27 | |
| 28 | Launch Library 2's anonymous limit is per IP, and Workers share egress IPs, |
| 29 | so anonymous requests from Cloudflare often get HTTP 429. With an API key: |
| 30 | |
| 31 | #+begin_src sh |
| 32 | wrangler secret put LL2_API_KEY |
| 33 | #+end_src |
| 34 | |
| 25 | 35 | * API |
| 26 | 36 | | Method and path | Body / result | |
| 27 | 37 | |- |
server/src/index.ts
+3 −1
| @@ -9,6 +9,8 @@ interface Env { |
| 9 | 9 | APNS_TEAM_ID: string; |
| 10 | 10 | APNS_TOPIC: string; |
| 11 | 11 | APNS_KEY: string; |
| 12 | /** Optional Launch Library 2 API key. */ |
| 13 | LL2_API_KEY?: string; |
| 12 | 14 | } |
| 13 | 15 | |
| 14 | 16 | interface DeviceRequest { |
| @@ -99,7 +101,7 @@ async function launches(env: Env, now: Date): Promise<Response> { |
| 99 | 101 | async function pollLaunches(env: Env, now: Date): Promise<void> { |
| 100 | 102 | let list: Launch[]; |
| 101 | 103 | try { |
| 102 | | list = await fetchUpcoming(); |
| 104 | list = await fetchUpcoming(env.LL2_API_KEY); |
| 103 | 105 | } catch (e) { |
| 104 | 106 | console.error("ll2", String(e)); |
| 105 | 107 | await env.DB.prepare("INSERT INTO feed_snapshots (feed, fetched_at, ok) VALUES ('ll2', ?1, 0) ON CONFLICT (feed) DO UPDATE SET fetched_at = ?1, ok = 0").bind(now.toISOString()).run(); |
server/src/ll2.ts
+5 −2
| @@ -63,8 +63,11 @@ export function normalize(r: Raw): Launch { |
| 63 | 63 | }; |
| 64 | 64 | } |
| 65 | 65 | |
| 66 | | export async function fetchUpcoming(): Promise<Launch[]> { |
| 67 | | const res = await fetch(UPCOMING_URL, { headers: { "user-agent": "moonrise-server (sh.krz.moonrise)" } }); |
| 66 | /** `apiKey` lifts the anonymous per-IP limit, which Cloudflare's shared egress IPs often exhaust (HTTP 429). */ |
| 67 | export async function fetchUpcoming(apiKey?: string): Promise<Launch[]> { |
| 68 | const headers: Record<string, string> = { "user-agent": "moonrise-server (sh.krz.moonrise)" }; |
| 69 | if (apiKey) headers.authorization = `Token ${apiKey}`; |
| 70 | const res = await fetch(UPCOMING_URL, { headers }); |
| 68 | 71 | if (!res.ok) throw new Error(`LL2 ${res.status}`); |
| 69 | 72 | const body = (await res.json()) as { results: Raw[] }; |
| 70 | 73 | return body.results.map(normalize); |
server/wrangler.jsonc
+1 −2
| @@ -7,8 +7,7 @@ |
| 7 | 7 | { |
| 8 | 8 | "binding": "DB", |
| 9 | 9 | "database_name": "moonrise", |
| 10 | | // Replace with the id from `wrangler d1 create moonrise` before deploying. |
| 11 | | "database_id": "00000000-0000-0000-0000-000000000000", |
| 10 | "database_id": "54a0c50d-2150-47ae-aa22-449e8204ad7b", |
| 12 | 11 | "migrations_dir": "migrations" |
| 13 | 12 | } |
| 14 | 13 | ], |