@alchemy.run/fly 2.0.0-beta.84 → 2.0.0-beta.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/lib/App.d.ts +27 -5
  2. package/lib/App.d.ts.map +1 -1
  3. package/lib/App.js +29 -8
  4. package/lib/App.js.map +1 -1
  5. package/lib/AuthProvider.d.ts +1 -1
  6. package/lib/AuthProvider.d.ts.map +1 -1
  7. package/lib/AuthProvider.js.map +1 -1
  8. package/lib/BindService.d.ts +7 -7
  9. package/lib/BindService.d.ts.map +1 -1
  10. package/lib/BindService.js +21 -28
  11. package/lib/BindService.js.map +1 -1
  12. package/lib/Bootstrap.d.ts.map +1 -1
  13. package/lib/Bootstrap.js +2 -1
  14. package/lib/Bootstrap.js.map +1 -1
  15. package/lib/Bucket.d.ts +38 -4
  16. package/lib/Bucket.d.ts.map +1 -1
  17. package/lib/Bucket.js +190 -37
  18. package/lib/Bucket.js.map +1 -1
  19. package/lib/BucketBinding.d.ts.map +1 -1
  20. package/lib/BucketBinding.js +1 -1
  21. package/lib/BucketBinding.js.map +1 -1
  22. package/lib/Catalog.d.ts.map +1 -1
  23. package/lib/Catalog.js.map +1 -1
  24. package/lib/Certificate.d.ts +25 -1
  25. package/lib/Certificate.d.ts.map +1 -1
  26. package/lib/Certificate.js +22 -4
  27. package/lib/Certificate.js.map +1 -1
  28. package/lib/Checkpoint.d.ts.map +1 -1
  29. package/lib/Checkpoint.js.map +1 -1
  30. package/lib/CheckpointHttp.d.ts.map +1 -1
  31. package/lib/CheckpointHttp.js.map +1 -1
  32. package/lib/ConnectPostgres.d.ts +1 -1
  33. package/lib/ConnectPostgres.d.ts.map +1 -1
  34. package/lib/ConnectPostgres.js.map +1 -1
  35. package/lib/ConnectPostgresHttp.d.ts.map +1 -1
  36. package/lib/ConnectPostgresHttp.js +1 -1
  37. package/lib/ConnectPostgresHttp.js.map +1 -1
  38. package/lib/Contributions.d.ts +1 -1
  39. package/lib/Contributions.d.ts.map +1 -1
  40. package/lib/Contributions.js.map +1 -1
  41. package/lib/Credentials.d.ts +5 -4
  42. package/lib/Credentials.d.ts.map +1 -1
  43. package/lib/Credentials.js +18 -18
  44. package/lib/Credentials.js.map +1 -1
  45. package/lib/Decrypt.d.ts.map +1 -1
  46. package/lib/Decrypt.js.map +1 -1
  47. package/lib/DecryptHttp.d.ts.map +1 -1
  48. package/lib/DecryptHttp.js.map +1 -1
  49. package/lib/DeleteObject.d.ts.map +1 -1
  50. package/lib/DeleteObject.js.map +1 -1
  51. package/lib/DeleteObjectHttp.d.ts.map +1 -1
  52. package/lib/DeleteObjectHttp.js.map +1 -1
  53. package/lib/Deployment.d.ts.map +1 -1
  54. package/lib/Deployment.js.map +1 -1
  55. package/lib/DeploymentImages.d.ts.map +1 -1
  56. package/lib/DeploymentImages.js +1 -1
  57. package/lib/DeploymentImages.js.map +1 -1
  58. package/lib/DeploymentState.d.ts.map +1 -1
  59. package/lib/DeploymentState.js.map +1 -1
  60. package/lib/Encrypt.d.ts.map +1 -1
  61. package/lib/Encrypt.js.map +1 -1
  62. package/lib/EncryptHttp.d.ts.map +1 -1
  63. package/lib/EncryptHttp.js.map +1 -1
  64. package/lib/Environment.d.ts.map +1 -1
  65. package/lib/Environment.js.map +1 -1
  66. package/lib/Errors.d.ts +1 -1
  67. package/lib/Errors.d.ts.map +1 -1
  68. package/lib/Errors.js.map +1 -1
  69. package/lib/Exec.d.ts.map +1 -1
  70. package/lib/Exec.js.map +1 -1
  71. package/lib/ExecHttp.d.ts.map +1 -1
  72. package/lib/ExecHttp.js.map +1 -1
  73. package/lib/GetObject.d.ts.map +1 -1
  74. package/lib/GetObject.js.map +1 -1
  75. package/lib/GetObjectHttp.d.ts.map +1 -1
  76. package/lib/GetObjectHttp.js.map +1 -1
  77. package/lib/GetSecret.d.ts.map +1 -1
  78. package/lib/GetSecret.js.map +1 -1
  79. package/lib/GetSecretHttp.d.ts.map +1 -1
  80. package/lib/GetSecretHttp.js.map +1 -1
  81. package/lib/HeadObject.d.ts.map +1 -1
  82. package/lib/HeadObject.js.map +1 -1
  83. package/lib/HeadObjectHttp.d.ts.map +1 -1
  84. package/lib/HeadObjectHttp.js.map +1 -1
  85. package/lib/IpAssignment.d.ts +24 -1
  86. package/lib/IpAssignment.d.ts.map +1 -1
  87. package/lib/IpAssignment.js +21 -4
  88. package/lib/IpAssignment.js.map +1 -1
  89. package/lib/ListObjectsV2.d.ts.map +1 -1
  90. package/lib/ListObjectsV2.js.map +1 -1
  91. package/lib/ListObjectsV2Http.d.ts.map +1 -1
  92. package/lib/ListObjectsV2Http.js.map +1 -1
  93. package/lib/ListSecrets.d.ts.map +1 -1
  94. package/lib/ListSecrets.js.map +1 -1
  95. package/lib/ListSecretsHttp.d.ts.map +1 -1
  96. package/lib/ListSecretsHttp.js.map +1 -1
  97. package/lib/Machine.d.ts +27 -2
  98. package/lib/Machine.d.ts.map +1 -1
  99. package/lib/Machine.js +18 -3
  100. package/lib/Machine.js.map +1 -1
  101. package/lib/MachineContainers.d.ts.map +1 -1
  102. package/lib/MachineContainers.js.map +1 -1
  103. package/lib/Metadata.d.ts.map +1 -1
  104. package/lib/Metadata.js.map +1 -1
  105. package/lib/MountVolume.d.ts +7 -2
  106. package/lib/MountVolume.d.ts.map +1 -1
  107. package/lib/MountVolume.js.map +1 -1
  108. package/lib/Postgres.d.ts +34 -2
  109. package/lib/Postgres.d.ts.map +1 -1
  110. package/lib/Postgres.js +26 -6
  111. package/lib/Postgres.js.map +1 -1
  112. package/lib/PostgresMigrations.d.ts +7 -22
  113. package/lib/PostgresMigrations.d.ts.map +1 -1
  114. package/lib/PostgresMigrations.js +37 -89
  115. package/lib/PostgresMigrations.js.map +1 -1
  116. package/lib/Providers.d.ts +1 -1
  117. package/lib/Providers.d.ts.map +1 -1
  118. package/lib/Providers.js.map +1 -1
  119. package/lib/PutObject.d.ts.map +1 -1
  120. package/lib/PutObject.js.map +1 -1
  121. package/lib/PutObjectHttp.d.ts.map +1 -1
  122. package/lib/PutObjectHttp.js.map +1 -1
  123. package/lib/ReadRedis.d.ts.map +1 -1
  124. package/lib/ReadRedis.js.map +1 -1
  125. package/lib/ReadRedisHttp.d.ts.map +1 -1
  126. package/lib/ReadRedisHttp.js.map +1 -1
  127. package/lib/ReadWriteRedis.d.ts.map +1 -1
  128. package/lib/ReadWriteRedis.js.map +1 -1
  129. package/lib/ReadWriteRedisHttp.d.ts.map +1 -1
  130. package/lib/ReadWriteRedisHttp.js.map +1 -1
  131. package/lib/Redis.d.ts +23 -3
  132. package/lib/Redis.d.ts.map +1 -1
  133. package/lib/Redis.js +37 -6
  134. package/lib/Redis.js.map +1 -1
  135. package/lib/RedisBinding.d.ts.map +1 -1
  136. package/lib/RedisBinding.js +1 -1
  137. package/lib/RedisBinding.js.map +1 -1
  138. package/lib/RedisHttp.d.ts +1 -1
  139. package/lib/RedisHttp.d.ts.map +1 -1
  140. package/lib/RedisHttp.js +4 -1
  141. package/lib/RedisHttp.js.map +1 -1
  142. package/lib/Region.d.ts.map +1 -1
  143. package/lib/Region.js.map +1 -1
  144. package/lib/Secret.d.ts +23 -2
  145. package/lib/Secret.d.ts.map +1 -1
  146. package/lib/Secret.js +17 -2
  147. package/lib/Secret.js.map +1 -1
  148. package/lib/SecretHttp.d.ts.map +1 -1
  149. package/lib/SecretHttp.js.map +1 -1
  150. package/lib/SecretKey.d.ts +23 -2
  151. package/lib/SecretKey.d.ts.map +1 -1
  152. package/lib/SecretKey.js +17 -2
  153. package/lib/SecretKey.js.map +1 -1
  154. package/lib/SecretKeyHttp.d.ts.map +1 -1
  155. package/lib/SecretKeyHttp.js.map +1 -1
  156. package/lib/Service.d.ts +104 -34
  157. package/lib/Service.d.ts.map +1 -1
  158. package/lib/Service.js +301 -54
  159. package/lib/Service.js.map +1 -1
  160. package/lib/Sign.d.ts.map +1 -1
  161. package/lib/Sign.js.map +1 -1
  162. package/lib/SignHttp.d.ts.map +1 -1
  163. package/lib/SignHttp.js.map +1 -1
  164. package/lib/Sprite.d.ts +24 -5
  165. package/lib/Sprite.d.ts.map +1 -1
  166. package/lib/Sprite.js +22 -5
  167. package/lib/Sprite.js.map +1 -1
  168. package/lib/SpriteHttp.d.ts.map +1 -1
  169. package/lib/SpriteHttp.js.map +1 -1
  170. package/lib/Verify.d.ts.map +1 -1
  171. package/lib/Verify.js.map +1 -1
  172. package/lib/VerifyHttp.d.ts.map +1 -1
  173. package/lib/VerifyHttp.js.map +1 -1
  174. package/lib/Volume.d.ts +4 -4
  175. package/lib/Volume.d.ts.map +1 -1
  176. package/lib/Volume.js +21 -5
  177. package/lib/Volume.js.map +1 -1
  178. package/lib/VolumeSnapshot.d.ts +32 -1
  179. package/lib/VolumeSnapshot.d.ts.map +1 -1
  180. package/lib/VolumeSnapshot.js +24 -1
  181. package/lib/VolumeSnapshot.js.map +1 -1
  182. package/lib/Website/AssetDeployment.d.ts.map +1 -1
  183. package/lib/Website/AssetDeployment.js +9 -9
  184. package/lib/Website/AssetDeployment.js.map +1 -1
  185. package/lib/Website/Astro.d.ts +8 -6
  186. package/lib/Website/Astro.d.ts.map +1 -1
  187. package/lib/Website/Astro.js +12 -13
  188. package/lib/Website/Astro.js.map +1 -1
  189. package/lib/Website/Foldkit.d.ts.map +1 -1
  190. package/lib/Website/Foldkit.js.map +1 -1
  191. package/lib/Website/FrameworkSite.d.ts +39 -2
  192. package/lib/Website/FrameworkSite.d.ts.map +1 -1
  193. package/lib/Website/FrameworkSite.js +46 -3
  194. package/lib/Website/FrameworkSite.js.map +1 -1
  195. package/lib/Website/Nextjs.d.ts.map +1 -1
  196. package/lib/Website/Nextjs.js.map +1 -1
  197. package/lib/Website/Nuxt.d.ts.map +1 -1
  198. package/lib/Website/Nuxt.js.map +1 -1
  199. package/lib/Website/Octane.d.ts.map +1 -1
  200. package/lib/Website/Octane.js.map +1 -1
  201. package/lib/Website/ReactRouter.d.ts.map +1 -1
  202. package/lib/Website/ReactRouter.js.map +1 -1
  203. package/lib/Website/SolidStart.d.ts.map +1 -1
  204. package/lib/Website/SolidStart.js.map +1 -1
  205. package/lib/Website/StaticSite.d.ts +10 -3
  206. package/lib/Website/StaticSite.d.ts.map +1 -1
  207. package/lib/Website/StaticSite.js +16 -4
  208. package/lib/Website/StaticSite.js.map +1 -1
  209. package/lib/Website/SvelteKit.d.ts.map +1 -1
  210. package/lib/Website/SvelteKit.js.map +1 -1
  211. package/lib/Website/TanStackStart.d.ts.map +1 -1
  212. package/lib/Website/TanStackStart.js.map +1 -1
  213. package/lib/Website/Vinext.d.ts.map +1 -1
  214. package/lib/Website/Vinext.js.map +1 -1
  215. package/lib/Website/Vite.d.ts +9 -0
  216. package/lib/Website/Vite.d.ts.map +1 -1
  217. package/lib/Website/Vite.js +9 -0
  218. package/lib/Website/Vite.js.map +1 -1
  219. package/lib/Website/Vocs.d.ts.map +1 -1
  220. package/lib/Website/Vocs.js.map +1 -1
  221. package/lib/Website/Waku.d.ts.map +1 -1
  222. package/lib/Website/Waku.js.map +1 -1
  223. package/lib/Website/index.d.ts.map +1 -1
  224. package/lib/Website/index.js.map +1 -1
  225. package/lib/WriteCertificates.d.ts.map +1 -1
  226. package/lib/WriteCertificates.js.map +1 -1
  227. package/lib/WriteCertificatesHttp.d.ts.map +1 -1
  228. package/lib/WriteCertificatesHttp.js +2 -2
  229. package/lib/WriteCertificatesHttp.js.map +1 -1
  230. package/lib/WriteRedis.d.ts.map +1 -1
  231. package/lib/WriteRedis.js.map +1 -1
  232. package/lib/WriteRedisHttp.d.ts.map +1 -1
  233. package/lib/WriteRedisHttp.js.map +1 -1
  234. package/lib/WriteSecret.d.ts.map +1 -1
  235. package/lib/WriteSecret.js.map +1 -1
  236. package/lib/WriteSecretHttp.d.ts.map +1 -1
  237. package/lib/WriteSecretHttp.js.map +1 -1
  238. package/lib/bluegreen.d.ts +5 -4
  239. package/lib/bluegreen.d.ts.map +1 -1
  240. package/lib/bluegreen.js +7 -5
  241. package/lib/bluegreen.js.map +1 -1
  242. package/lib/hosted.d.ts +19 -4
  243. package/lib/hosted.d.ts.map +1 -1
  244. package/lib/hosted.js +101 -11
  245. package/lib/hosted.js.map +1 -1
  246. package/lib/index.d.ts.map +1 -1
  247. package/lib/index.js.map +1 -1
  248. package/lib/internal/Oidc.d.ts +15 -0
  249. package/lib/internal/Oidc.d.ts.map +1 -0
  250. package/lib/internal/Oidc.js +73 -0
  251. package/lib/internal/Oidc.js.map +1 -0
  252. package/lib/internal/RemoteBuilder.d.ts.map +1 -1
  253. package/lib/internal/RemoteBuilder.js +21 -8
  254. package/lib/internal/RemoteBuilder.js.map +1 -1
  255. package/lib/leases.d.ts +6 -2
  256. package/lib/leases.d.ts.map +1 -1
  257. package/lib/leases.js +25 -5
  258. package/lib/leases.js.map +1 -1
  259. package/lib/ports.d.ts.map +1 -1
  260. package/lib/ports.js.map +1 -1
  261. package/lib/replicas.d.ts +39 -27
  262. package/lib/replicas.d.ts.map +1 -1
  263. package/lib/replicas.js +63 -8
  264. package/lib/replicas.js.map +1 -1
  265. package/lib/rpc.d.ts +14 -12
  266. package/lib/rpc.d.ts.map +1 -1
  267. package/lib/rpc.js +38 -40
  268. package/lib/rpc.js.map +1 -1
  269. package/package.json +15 -15
  270. package/src/App.ts +41 -10
  271. package/src/BindService.ts +28 -36
  272. package/src/Bootstrap.ts +2 -0
  273. package/src/Bucket.ts +278 -45
  274. package/src/BucketBinding.ts +1 -1
  275. package/src/Certificate.ts +36 -5
  276. package/src/ConnectPostgres.ts +1 -1
  277. package/src/ConnectPostgresHttp.ts +1 -1
  278. package/src/Credentials.ts +18 -41
  279. package/src/DeploymentImages.ts +1 -1
  280. package/src/Errors.ts +1 -1
  281. package/src/IpAssignment.ts +35 -5
  282. package/src/Machine.ts +39 -4
  283. package/src/MountVolume.ts +5 -2
  284. package/src/Postgres.ts +50 -8
  285. package/src/PostgresMigrations.ts +39 -99
  286. package/src/Redis.ts +54 -9
  287. package/src/RedisBinding.ts +1 -1
  288. package/src/RedisHttp.ts +4 -1
  289. package/src/Secret.ts +32 -4
  290. package/src/SecretKey.ts +32 -4
  291. package/src/Service.ts +441 -88
  292. package/src/Sprite.ts +45 -17
  293. package/src/Volume.ts +30 -6
  294. package/src/VolumeSnapshot.ts +40 -2
  295. package/src/Website/AssetDeployment.ts +13 -15
  296. package/src/Website/Astro.ts +20 -15
  297. package/src/Website/FrameworkSite.ts +92 -6
  298. package/src/Website/StaticSite.ts +27 -7
  299. package/src/Website/Vite.ts +9 -0
  300. package/src/WriteCertificatesHttp.ts +2 -2
  301. package/src/bluegreen.ts +11 -5
  302. package/src/hosted.ts +121 -17
  303. package/src/internal/Oidc.ts +99 -0
  304. package/src/internal/RemoteBuilder.ts +37 -19
  305. package/src/leases.ts +35 -7
  306. package/src/replicas.ts +70 -9
  307. package/src/rpc.ts +37 -40
package/src/replicas.ts CHANGED
@@ -42,7 +42,7 @@ const waitBackoff = Schedule.exponential("500 millis");
42
42
  const SERVICE_CHECK_NAME_PREFIX = "servicecheck-";
43
43
 
44
44
  export class ReplicaNotCreated extends Data.TaggedError("Fly.ReplicaNotCreated")<{
45
- name: string;
45
+ resourceName: string;
46
46
  appName: string;
47
47
  }> {}
48
48
 
@@ -115,7 +115,7 @@ export const gone = (machine: FlyMachine | undefined) =>
115
115
  export const getMachineById = (appName: string, machineId: string) =>
116
116
  machines.getMachine({ app_name: appName, machine_id: machineId }).pipe(
117
117
  Effect.map((machine) => (gone(machine) ? undefined : machine)),
118
- Effect.catchTag("NotFound", () => Effect.succeed(undefined)),
118
+ Effect.catchTag("NotFound", () => Effect.undefined),
119
119
  );
120
120
 
121
121
  /**
@@ -137,7 +137,7 @@ const getLeasedMachine = (appName: string, machineId: string) =>
137
137
  const getLeasedMachineById = (appName: string, machineId: string) =>
138
138
  getLeasedMachine(appName, machineId).pipe(
139
139
  Effect.map((machine) => (gone(machine) ? undefined : machine)),
140
- Effect.catchTag("NotFound", () => Effect.succeed(undefined)),
140
+ Effect.catchTag("NotFound", () => Effect.undefined),
141
141
  );
142
142
 
143
143
  export const listMachinesByApp = (appName: string) =>
@@ -653,6 +653,8 @@ export const leaseSnapshot = Effect.fn(function* (
653
653
  (machine) =>
654
654
  Effect.gen(function* () {
655
655
  if (!machine.id || machine.host_status === "unreachable") return;
656
+ // Already deleted by this operation (a scale-down retired it).
657
+ if (leases.wasRemoved(machine.id)) return;
656
658
  const id = machine.id;
657
659
  yield* leases.acquire([id]).pipe(
658
660
  Effect.tap(() => Effect.sync(() => leased.add(id))),
@@ -670,7 +672,7 @@ export const leaseSnapshot = Effect.fn(function* (
670
672
  ).pipe(Effect.timeout("30 seconds"));
671
673
  const observed: FlyMachine[] = [];
672
674
  for (const machine of ordered) {
673
- if (!machine.id) continue;
675
+ if (!machine.id || leases.wasRemoved(machine.id)) continue;
674
676
  if (machine.host_status === "unreachable") {
675
677
  observed.push(machine);
676
678
  continue;
@@ -1163,7 +1165,7 @@ const reconcileLeasedReplicas = Effect.fn(function* (
1163
1165
  );
1164
1166
  const volumeId = volume?.id;
1165
1167
  if (volumeId === undefined) {
1166
- return yield* new ReplicaNotCreated({ name, appName: input.appName });
1168
+ return yield* new ReplicaNotCreated({ resourceName: name, appName: input.appName });
1167
1169
  }
1168
1170
  usedVolumeIds.add(volumeId);
1169
1171
  mounts.push({ volume: volumeId, path: group.disk.path });
@@ -1187,7 +1189,17 @@ const reconcileLeasedReplicas = Effect.fn(function* (
1187
1189
  skip_launch: input.skipLaunch === true ? true : undefined,
1188
1190
  min_secrets_version: input.minSecretsVersion,
1189
1191
  })
1190
- .pipe(Effect.catchTag("Conflict", () => Effect.succeed(undefined)));
1192
+ .pipe(
1193
+ // A storage host out of capacity may still have made the Machine;
1194
+ // names are unique per App, so a retry that made it before hits
1195
+ // Conflict and the listing below finds it.
1196
+ Effect.retry({
1197
+ while: (error) => error._tag === "StorageHostCapacity",
1198
+ schedule: Schedule.spaced("5 seconds"),
1199
+ times: 6,
1200
+ }),
1201
+ Effect.catchTag("Conflict", () => Effect.undefined),
1202
+ );
1191
1203
  current =
1192
1204
  created ??
1193
1205
  (yield* listMachinesByApp(input.appName).pipe(
@@ -1200,11 +1212,11 @@ const reconcileLeasedReplicas = Effect.fn(function* (
1200
1212
  ),
1201
1213
  ));
1202
1214
  if (current === undefined || current.id === undefined) {
1203
- return yield* new ReplicaNotCreated({ name, appName: input.appName });
1215
+ return yield* new ReplicaNotCreated({ resourceName: name, appName: input.appName });
1204
1216
  }
1205
1217
  const observed = (yield* leaseSnapshot(input.appName, [current], leases))[0];
1206
1218
  if (!observed || input.configDrifted(observed, { mounts, metadata })) {
1207
- return yield* new ReplicaNotCreated({ name, appName: input.appName });
1219
+ return yield* new ReplicaNotCreated({ resourceName: name, appName: input.appName });
1208
1220
  }
1209
1221
  current = observed;
1210
1222
  } else if (
@@ -1243,7 +1255,7 @@ const reconcileLeasedReplicas = Effect.fn(function* (
1243
1255
  !sameChecks(observed.config?.checks, config.checks) ||
1244
1256
  !sameStopConfig(observed.config?.stop_config, config.stop_config)
1245
1257
  )
1246
- return yield* new ReplicaNotCreated({ appName: input.appName, name });
1258
+ return yield* new ReplicaNotCreated({ appName: input.appName, resourceName: name });
1247
1259
  return observed;
1248
1260
  }).pipe(
1249
1261
  Effect.retry({
@@ -1478,3 +1490,52 @@ export const observeReplicaSet = Effect.fn(function* (input: {
1478
1490
  };
1479
1491
  return undefined;
1480
1492
  });
1493
+
1494
+ /**
1495
+ * The Machines a lookup names: those whose name (or Alchemy base name) is
1496
+ * `name`, or `name-N` for a replica, or every Machine in the App when
1497
+ * `name` is omitted. Of an Alchemy deployment, only the newest generation
1498
+ * that finished rolling out counts; a Machine without that metadata
1499
+ * counts as it is. `undefined` when nothing in the App matches.
1500
+ */
1501
+ export const lookupReplicaSet = Effect.fn(function* (input: { appName: string; name?: string }) {
1502
+ const named = (yield* listMachinesByApp(input.appName)).filter((machine) => {
1503
+ if (input.name === undefined) return true;
1504
+ const base = machine.config?.metadata?.[alchemyMetadataKeys.baseName];
1505
+ return (
1506
+ base === input.name ||
1507
+ machine.name === input.name ||
1508
+ (base === undefined && machine.name?.startsWith(`${input.name}-`) === true)
1509
+ );
1510
+ });
1511
+ const active = named.filter((machine) => {
1512
+ const phase = machine.config?.metadata?.[alchemyMetadataKeys.phase];
1513
+ return (phase === undefined || phase === "active") && machine.cordoned !== true;
1514
+ });
1515
+ const sequenceOf = (machine: FlyMachine) =>
1516
+ Number(machine.config?.metadata?.[alchemyMetadataKeys.sequence] ?? 0);
1517
+ const newest = Math.max(0, ...active.map(sequenceOf));
1518
+ const listed = active
1519
+ .filter((machine) => sequenceOf(machine) === newest)
1520
+ .sort((a, b) => replicaIndexOf(a) - replicaIndexOf(b));
1521
+ if (listed.length === 0) return undefined;
1522
+ const volumesById = new Map<string, FlyVolume>();
1523
+ for (const machine of listed) {
1524
+ for (const mount of machine.config?.mounts ?? []) {
1525
+ const volumeId = mount.volume;
1526
+ if (volumeId === undefined || volumesById.has(volumeId)) continue;
1527
+ const volume = yield* getVolumeById(input.appName, volumeId);
1528
+ if (volume !== undefined) volumesById.set(volumeId, volume);
1529
+ }
1530
+ }
1531
+ const replicas = listed.map((machine) => toReplica(machine, volumesById));
1532
+ return toReplicaSet(
1533
+ replicas,
1534
+ input.appName,
1535
+ listed[0]?.config?.metadata?.[alchemyMetadataKeys.baseName] ??
1536
+ input.name ??
1537
+ replicas[0]?.name ??
1538
+ "",
1539
+ listed[0]?.config?.services,
1540
+ );
1541
+ });
package/src/rpc.ts CHANGED
@@ -1,13 +1,18 @@
1
1
  import * as crypto from "node:crypto";
2
2
  import type { HttpEffect } from "alchemy/Http";
3
3
  import { RPC_PATH_PREFIX, serveRpc } from "alchemy/Rpc";
4
+ import * as Clock from "effect/Clock";
5
+ import * as Config from "effect/Config";
4
6
  import * as Effect from "effect/Effect";
5
7
  import * as FileSystem from "effect/FileSystem";
6
8
  import { HttpServerRequest } from "effect/http/HttpServerRequest";
7
9
  import * as HttpServerResponse from "effect/http/HttpServerResponse";
8
10
 
9
- /** Machine env var holding the Service's caller token. */
10
- export const RPC_TOKEN_ENV = "ALCHEMY_FLY_RPC_TOKEN";
11
+ /**
12
+ * Machine env var holding the Apps allowed to call the Service's methods,
13
+ * comma-separated: the Apps of the Services that bind it.
14
+ */
15
+ export const RPC_CALLERS_ENV = "ALCHEMY_FLY_RPC_CALLERS";
11
16
  /** Machine env var holding the organization callers must belong to. */
12
17
  export const RPC_ORG_ENV = "ALCHEMY_FLY_ORG";
13
18
  /**
@@ -15,8 +20,6 @@ export const RPC_ORG_ENV = "ALCHEMY_FLY_ORG";
15
20
  * Requests on it must come from Fly's private network.
16
21
  */
17
22
  export const BINDING_PORT_ENV = "ALCHEMY_FLY_BINDING_PORT";
18
- /** Request header carrying the caller token. */
19
- export const RPC_TOKEN_HEADER = "x-alchemy-rpc-token";
20
23
  /** Published port Alchemy adds when a Service has no plain-HTTP port. */
21
24
  export const DEFAULT_BINDING_PORT = 7780;
22
25
 
@@ -26,21 +29,16 @@ const FLY_SRC_SIGNATURE_HEADER = "fly-src-signature";
26
29
  const FLY_SRC_PUBLIC_KEY = "/.fly/fly-src.pub";
27
30
  /** DER prefix that wraps a raw 32-byte ed25519 key as SubjectPublicKeyInfo. */
28
31
  const ED25519_SPKI_PREFIX = "302a300506032b6570032100";
29
- /** Oldest accepted `Fly-Src` timestamp, in seconds. */
30
- const MAX_FLY_SRC_AGE_SECONDS = 300;
32
+ /**
33
+ * Oldest accepted `Fly-Src` timestamp, in seconds. The signature covers
34
+ * only the header, so a short window limits replay; Fly suggests 10s, and
35
+ * the rest allows for clock skew between its proxy and the Machine.
36
+ */
37
+ const MAX_FLY_SRC_AGE_SECONDS = 30;
31
38
 
32
39
  const header = (headers: Record<string, string | undefined>, name: string): string =>
33
40
  headers[name] ?? "";
34
41
 
35
- const tokensEqual = (left: string, right: string): boolean => {
36
- if (left.length !== right.length) return false;
37
- let mismatch = 0;
38
- for (let i = 0; i < left.length; i++) {
39
- mismatch |= left.charCodeAt(i) ^ right.charCodeAt(i);
40
- }
41
- return mismatch === 0;
42
- };
43
-
44
42
  let publicKey: crypto.KeyObject | undefined;
45
43
 
46
44
  /** Load Fly's `Fly-Src` verification key once per instance. */
@@ -64,40 +62,42 @@ const loadPublicKey = Effect.gen(function* () {
64
62
  });
65
63
 
66
64
  /**
67
- * Whether the request carries a valid `Fly-Src` signature from Fly's proxy
68
- * for a caller in `org`. Fly adds the header only to requests that arrive
69
- * over its private network, so a public request can never pass.
65
+ * The calling App named by a valid `Fly-Src` signature from Fly's proxy for
66
+ * a caller in `org`, or `undefined`. Fly adds the header only to requests
67
+ * that arrive over its private network and signs who sent them, so a public
68
+ * request can never pass and no caller can claim another App.
70
69
  */
71
70
  export const verifyFlySrc = (headers: Record<string, string | undefined>, org: string) =>
72
71
  Effect.gen(function* () {
73
72
  const source = header(headers, FLY_SRC_HEADER);
74
73
  const signature = header(headers, FLY_SRC_SIGNATURE_HEADER);
75
- if (source.length === 0 || signature.length === 0 || org.length === 0) return false;
74
+ if (source.length === 0 || signature.length === 0 || org.length === 0) return undefined;
76
75
  const key = yield* loadPublicKey;
77
- if (key === undefined) return false;
76
+ if (key === undefined) return undefined;
78
77
  const valid = yield* Effect.try(() =>
79
78
  crypto.verify(null, Buffer.from(source), key, Buffer.from(signature, "base64")),
80
79
  ).pipe(Effect.orElseSucceed(() => false));
81
- if (!valid) return false;
80
+ if (!valid) return undefined;
82
81
  const fields = Object.fromEntries(
83
82
  source.split(";").map((pair) => {
84
83
  const at = pair.indexOf("=");
85
84
  return [pair.slice(0, at), pair.slice(at + 1)];
86
85
  }),
87
86
  );
88
- const now = yield* Effect.sync(() => Date.now() / 1000);
87
+ const now = (yield* Clock.currentTimeMillis) / 1000;
89
88
  const age = now - Number(fields.ts);
90
- return fields.org === org && age >= -60 && age <= MAX_FLY_SRC_AGE_SECONDS;
89
+ const fresh = age >= -MAX_FLY_SRC_AGE_SECONDS && age <= MAX_FLY_SRC_AGE_SECONDS;
90
+ return fields.org === org && fresh && fields.app ? fields.app : undefined;
91
91
  });
92
92
 
93
93
  const unauthorized = HttpServerResponse.text("Unauthorized", { status: 401 });
94
94
 
95
95
  /**
96
96
  * Serve a Service's RPC methods on `/__rpc__/*` and everything else with
97
- * `fallback`. RPC calls must carry the Service's caller token and a valid
98
- * `Fly-Src` signature from the Service's organization. Every request on the
99
- * port Alchemy added for bindings must carry the `Fly-Src` signature, so
100
- * that port never answers the public internet.
97
+ * `fallback`. RPC calls must carry a valid `Fly-Src` signature from the
98
+ * Service's organization naming an App that binds the Service. Every
99
+ * request on the port Alchemy added for bindings must carry the `Fly-Src`
100
+ * signature, so that port never answers the public internet.
101
101
  */
102
102
  export const serveFlyRpc = <Req = never>(
103
103
  shape: Record<string, unknown>,
@@ -107,23 +107,20 @@ export const serveFlyRpc = <Req = never>(
107
107
  const request = yield* HttpServerRequest;
108
108
  const headers = request.headers as Record<string, string | undefined>;
109
109
  const isRpc = request.url.includes(RPC_PATH_PREFIX);
110
- const env = yield* Effect.sync(() => ({
111
- bindingPort: process.env[BINDING_PORT_ENV] ?? "",
112
- org: process.env[RPC_ORG_ENV] ?? "",
113
- token: process.env[RPC_TOKEN_ENV] ?? "",
114
- }));
110
+ const readEnv = (name: string) => Config.String(name).pipe(Effect.orElseSucceed(() => ""));
111
+ const env = {
112
+ bindingPort: yield* readEnv(BINDING_PORT_ENV),
113
+ org: yield* readEnv(RPC_ORG_ENV),
114
+ callers: yield* readEnv(RPC_CALLERS_ENV),
115
+ };
115
116
  const bindingPort = env.bindingPort;
116
117
  const onBindingPort =
117
118
  bindingPort.length > 0 && header(headers, "fly-forwarded-port") === bindingPort;
118
119
  if (!isRpc && !onBindingPort) return yield* fallback;
119
- if (!(yield* verifyFlySrc(headers, env.org))) return unauthorized;
120
- if (isRpc) {
121
- const expected = env.token;
122
- const provided = header(headers, RPC_TOKEN_HEADER);
123
- if (expected.length === 0 || provided.length === 0 || !tokensEqual(provided, expected)) {
124
- return unauthorized;
125
- }
126
- }
120
+ const caller = yield* verifyFlySrc(headers, env.org);
121
+ if (caller === undefined) return unauthorized;
122
+ // Methods answer only the Apps of the Services that bind this one.
123
+ if (isRpc && !env.callers.split(",").includes(caller)) return unauthorized;
127
124
  return yield* serveRpc(shape, fallback);
128
125
  });
129
126