| 1 | /** |
| 2 | * A Hetzner Cloud app that exercises the full module-scope graph: |
| 3 | * |
| 4 | * - `Net` — private Network (`src/shared.ts`) |
| 5 | * - `Box` — Server attached to the Network |
| 6 | * - `Data` — Volume both Services mount via `Hetzner.MountVolume` |
| 7 | * - `Wall` — Firewall (SSH + API) applied to the Server |
| 8 | * - `Edge` — Load Balancer targeting the Server over the private IP |
| 9 | * - `DnsZone` / `AppRecord` — optional DNS (`HETZNER_ZONE` for a real apex) |
| 10 | * - `Api` — HTTP Service on port 3000 (`src/api.ts`) |
| 11 | * - `Worker` — background Service that writes the volume marker (`src/worker.ts`) |
| 12 | */ |
| 13 | import * as Alchemy from "alchemy"; |
| 14 | import * as Hetzner from "alchemy/Hetzner"; |
| 15 | import * as Output from "alchemy/Output"; |
| 16 | import * as Effect from "effect/Effect"; |
| 17 | import Api from "./src/api.ts"; |
| 18 | import { |
| 19 | API_PORT, |
| 20 | AppRecord, |
| 21 | Box, |
| 22 | Data, |
| 23 | DnsZone, |
| 24 | Edge, |
| 25 | Net, |
| 26 | Wall, |
| 27 | } from "./src/shared.ts"; |
| 28 | import Worker from "./src/worker.ts"; |
| 29 | |
| 30 | export default Alchemy.Stack( |
| 31 | "HetznerApp", |
| 32 | { |
| 33 | providers: Hetzner.providers(), |
| 34 | state: Alchemy.localState(), |
| 35 | }, |
| 36 | Effect.gen(function* () { |
| 37 | const network = yield* Net; |
| 38 | const server = yield* Box; |
| 39 | const volume = yield* Data; |
| 40 | const firewall = yield* Wall; |
| 41 | const lb = yield* Edge; |
| 42 | const worker = yield* Worker; |
| 43 | const api = yield* Api; |
| 44 | const dns = |
| 45 | process.env.HETZNER_ZONE !== undefined && |
| 46 | process.env.HETZNER_ZONE.length > 0 |
| 47 | ? yield* Effect.all({ zone: DnsZone, records: AppRecord }) |
| 48 | : undefined; |
| 49 | |
| 50 | return { |
| 51 | networkId: network.networkId, |
| 52 | serverId: server.serverId, |
| 53 | serverIpv4: server.ipv4, |
| 54 | volumeId: volume.id, |
| 55 | firewallId: firewall.id, |
| 56 | loadBalancerId: lb.id, |
| 57 | loadBalancerIpv4: lb.ipv4, |
| 58 | loadBalancerUrl: Output.interpolate`http://${lb.ipv4}`, |
| 59 | zoneId: dns?.zone.zoneId, |
| 60 | zoneName: dns?.zone.name, |
| 61 | recordName: dns?.records.name, |
| 62 | apiUrl: api.url, |
| 63 | apiPort: api.port, |
| 64 | workerUnit: worker.unitName, |
| 65 | enqueueExample: Output.interpolate`http://${server.ipv4}:${API_PORT}`, |
| 66 | }; |
| 67 | }), |
| 68 | ); |