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 | Simulator: an iOS 26+ runtime is required. Push registration needs a |
11 | Simulator: an iOS 26+ runtime is required. Push registration needs a |
| 12 | signed build (team =ZCNAX3VL9D=); the APNs device token is logged under |
12 | signed build (team =ZCNAX3VL9D=); the APNs device token is logged under |
| 13 | subsystem =sh.krz.moonrise=. |
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 | settings: |
32 | settings: |
| 33 | configs: |
33 | configs: |
| 34 | Debug: |
34 | Debug: |
| 35 | MOONRISE_API_URL: http://localhost:8787 |
35 | MOONRISE_API_URL: https://moonrise-server.audit-labs.workers.dev |
| 36 | Release: |
36 | Release: |
| 37 | MOONRISE_API_URL: "" |
37 | MOONRISE_API_URL: https://moonrise-server.audit-labs.workers.dev |
| 38 | base: |
38 | base: |
| 39 | PRODUCT_BUNDLE_IDENTIFIER: sh.krz.moonrise |
39 | PRODUCT_BUNDLE_IDENTIFIER: sh.krz.moonrise |
| 40 | DEVELOPMENT_TEAM: ZCNAX3VL9D |
40 | DEVELOPMENT_TEAM: ZCNAX3VL9D |
PLAN.org
+11
| @@ -374,6 +374,17 @@ MR !11. |
| 374 | - Not done: tap a planet or probe for its Library page, Earth–Moon inset, |
374 | - Not done: tap a planet or probe for its Library page, Earth–Moon inset, |
| 375 | active missions (Juno, Europa Clipper, JUICE, Psyche, Parker, Lucy). |
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 | * roadmap |
388 | * roadmap |
| 378 | ** v1: now |
389 | ** v1: now |
| 379 | Sky, System, Sun, Launches, Library core (above). |
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 | against a deployed Worker. |
15 | against a deployed Worker. |
| 16 | |
16 | |
| 17 | * deploy |
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 | #+begin_src sh |
21 | #+begin_src sh |
| 19 | wrangler d1 create moonrise # put the id in wrangler.jsonc |
22 | wrangler d1 create moonrise # put the id in wrangler.jsonc |
| 20 | wrangler d1 migrations apply moonrise --remote |
23 | wrangler d1 migrations apply moonrise --remote |
| @@ -22,6 +25,13 @@ wrangler deploy |
| 22 | wrangler secret put APNS_KEY < AuthKey_644XS4F2RG.p8 |
25 | wrangler secret put APNS_KEY < AuthKey_644XS4F2RG.p8 |
| 23 | #+end_src |
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 | * API |
35 | * API |
| 26 | | Method and path | Body / result | |
36 | | Method and path | Body / result | |
| 27 | |- |
37 | |- |
server/src/index.ts
+3 −1
| @@ -9,6 +9,8 @@ interface Env { |
| 9 | APNS_TEAM_ID: string; |
9 | APNS_TEAM_ID: string; |
| 10 | APNS_TOPIC: string; |
10 | APNS_TOPIC: string; |
| 11 | APNS_KEY: string; |
11 | APNS_KEY: string; |
| |
12 | /** Optional Launch Library 2 API key. */ |
| |
13 | LL2_API_KEY?: string; |
| 12 | } |
14 | } |
| 13 | |
15 | |
| 14 | interface DeviceRequest { |
16 | interface DeviceRequest { |
| @@ -99,7 +101,7 @@ async function launches(env: Env, now: Date): Promise<Response> { |
| 99 | async function pollLaunches(env: Env, now: Date): Promise<void> { |
101 | async function pollLaunches(env: Env, now: Date): Promise<void> { |
| 100 | let list: Launch[]; |
102 | let list: Launch[]; |
| 101 | try { |
103 | try { |
| 102 | list = await fetchUpcoming(); |
104 | list = await fetchUpcoming(env.LL2_API_KEY); |
| 103 | } catch (e) { |
105 | } catch (e) { |
| 104 | console.error("ll2", String(e)); |
106 | console.error("ll2", String(e)); |
| 105 | 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(); |
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[]> { |
66 | /** `apiKey` lifts the anonymous per-IP limit, which Cloudflare's shared egress IPs often exhaust (HTTP 429). */ |
| 67 | const res = await fetch(UPCOMING_URL, { headers: { "user-agent": "moonrise-server (sh.krz.moonrise)" } }); |
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 | if (!res.ok) throw new Error(`LL2 ${res.status}`); |
71 | if (!res.ok) throw new Error(`LL2 ${res.status}`); |
| 69 | const body = (await res.json()) as { results: Raw[] }; |
72 | const body = (await res.json()) as { results: Raw[] }; |
| 70 | return body.results.map(normalize); |
73 | return body.results.map(normalize); |
server/wrangler.jsonc
+1 −2
| @@ -7,8 +7,7 @@ |
| 7 | { |
7 | { |
| 8 | "binding": "DB", |
8 | "binding": "DB", |
| 9 | "database_name": "moonrise", |
9 | "database_name": "moonrise", |
| 10 | // Replace with the id from `wrangler d1 create moonrise` before deploying. |
10 | "database_id": "54a0c50d-2150-47ae-aa22-449e8204ad7b", |
| 11 | "database_id": "00000000-0000-0000-0000-000000000000", |
| |
| 12 | "migrations_dir": "migrations" |
11 | "migrations_dir": "migrations" |
| 13 | } |
12 | } |
| 14 | ], |
13 | ], |